1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.widget;
18 
19 import android.annotation.AttrRes;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.StyleRes;
23 import android.app.ActivityManager;
24 import android.content.Context;
25 import android.graphics.drawable.BitmapDrawable;
26 import android.graphics.drawable.Drawable;
27 import android.graphics.drawable.Icon;
28 import android.net.Uri;
29 import android.util.AttributeSet;
30 import android.util.Log;
31 import android.widget.ImageView;
32 import android.widget.RemoteViews;
33 import android.widget.flags.Flags;
34 
35 import com.android.internal.R;
36 
37 /**
38  * An ImageView used by BigPicture Notifications to correctly resolve the Uri in an Icon using the
39  * LocalImageResolver, allowing it to support animated drawables which are not supported by
40  * Icon.loadDrawable().
41  */
42 @RemoteViews.RemoteView
43 public class BigPictureNotificationImageView extends ImageView implements
44         NotificationDrawableConsumer {
45 
46     private static final String TAG = BigPictureNotificationImageView.class.getSimpleName();
47 
48     private final int mMaximumDrawableWidth;
49     private final int mMaximumDrawableHeight;
50 
51     private NotificationIconManager mIconManager;
52 
BigPictureNotificationImageView(@onNull Context context)53     public BigPictureNotificationImageView(@NonNull Context context) {
54         this(context, null, 0, 0);
55     }
56 
BigPictureNotificationImageView(@onNull Context context, @Nullable AttributeSet attrs)57     public BigPictureNotificationImageView(@NonNull Context context, @Nullable AttributeSet attrs) {
58         this(context, attrs, 0, 0);
59     }
60 
BigPictureNotificationImageView(@onNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr)61     public BigPictureNotificationImageView(@NonNull Context context, @Nullable AttributeSet attrs,
62             @AttrRes int defStyleAttr) {
63         this(context, attrs, defStyleAttr, 0);
64     }
65 
BigPictureNotificationImageView(@onNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes)66     public BigPictureNotificationImageView(@NonNull Context context, @Nullable AttributeSet attrs,
67             @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
68         super(context, attrs, defStyleAttr, defStyleRes);
69         boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
70         mMaximumDrawableWidth = context.getResources().getDimensionPixelSize(
71                 isLowRam ? R.dimen.notification_big_picture_max_width_low_ram
72                         : R.dimen.notification_big_picture_max_width);
73         mMaximumDrawableHeight = context.getResources().getDimensionPixelSize(
74                 isLowRam ? R.dimen.notification_big_picture_max_height_low_ram
75                         : R.dimen.notification_big_picture_max_height);
76     }
77 
78 
79     /**
80      * Sets an {@link NotificationIconManager} on this ImageView, which handles the loading of
81      * icons, instead of using the {@link LocalImageResolver} directly.
82      * If set, it overrides the behaviour of {@link #setImageIconAsync} and {@link #setImageIcon},
83      * and it expects that the content of this imageView is only updated calling these two methods.
84      *
85      * @param iconManager to be called, when the icon is updated
86      */
setIconManager(NotificationIconManager iconManager)87     public void setIconManager(NotificationIconManager iconManager) {
88         mIconManager = iconManager;
89     }
90 
91     @Override
92     @android.view.RemotableViewMethod(asyncImpl = "setImageURIAsync")
setImageURI(@ullable Uri uri)93     public void setImageURI(@Nullable Uri uri) {
94         setImageDrawable(loadImage(uri));
95     }
96 
97     /** @hide **/
setImageURIAsync(@ullable Uri uri)98     public Runnable setImageURIAsync(@Nullable Uri uri) {
99         final Drawable drawable = loadImage(uri);
100         return () -> setImageDrawable(drawable);
101     }
102 
103     @Override
104     @android.view.RemotableViewMethod(asyncImpl = "setImageIconAsync")
setImageIcon(@ullable Icon icon)105     public void setImageIcon(@Nullable Icon icon) {
106         if (mIconManager != null) {
107             mIconManager.updateIcon(this, icon).run();
108             return;
109         }
110         // old code path
111         setImageDrawable(loadImage(icon));
112     }
113 
114     /** @hide **/
setImageIconAsync(@ullable Icon icon)115     public Runnable setImageIconAsync(@Nullable Icon icon) {
116         if (mIconManager != null) {
117             return mIconManager.updateIcon(this, icon);
118         }
119         // old code path
120         final Drawable drawable = loadImage(icon);
121         return () -> setImageDrawable(drawable);
122     }
123 
124     @Override
setImageDrawable(@ullable Drawable drawable)125     public void setImageDrawable(@Nullable Drawable drawable) {
126         if (drawable instanceof BitmapDrawable bitmapDrawable) {
127             if (bitmapDrawable.getBitmap() == null) {
128                 if (Flags.bigPictureStyleDiscardEmptyIconBitmapDrawables()) {
129                     Log.e(TAG, "discarding BitmapDrawable with null Bitmap (invalid image file?)");
130                     drawable = null;
131                 } else {
132                     Log.e(TAG, "setting BitmapDrawable with null Bitmap (invalid image file?)");
133                 }
134             }
135         }
136 
137         super.setImageDrawable(drawable);
138     }
139 
loadImage(Uri uri)140     private Drawable loadImage(Uri uri) {
141         if (uri == null) return null;
142         return LocalImageResolver.resolveImage(uri, mContext, mMaximumDrawableWidth,
143                 mMaximumDrawableHeight);
144     }
145 
loadImage(Icon icon)146     private Drawable loadImage(Icon icon) {
147         if (icon == null) return null;
148 
149         Drawable drawable = LocalImageResolver.resolveImage(icon, mContext, mMaximumDrawableWidth,
150                 mMaximumDrawableHeight);
151         if (drawable != null) {
152             return drawable;
153         }
154 
155         drawable = icon.loadDrawable(mContext);
156         if (drawable != null) {
157             return drawable;
158         }
159 
160         Log.e(TAG, "Couldn't load drawable for icon: " + icon);
161         return null;
162     }
163 }
164