• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.Nullable;
20 import android.app.Flags;
21 import android.content.Context;
22 import android.graphics.Rect;
23 import android.graphics.drawable.Drawable;
24 import android.graphics.drawable.Icon;
25 import android.util.AttributeSet;
26 import android.view.RemotableViewMethod;
27 import android.widget.RemoteViews;
28 
29 /**
30  * An image view that holds the icon displayed at the start of a notification row.
31  * This can generally either display the "small icon" of a notification set via
32  * {@link this#setImageIcon(Icon)}, or an app icon controlled and fetched by the provider set
33  * through {@link this#setIconProvider(NotificationIconProvider)}.
34  */
35 @RemoteViews.RemoteView
36 public class NotificationRowIconView extends CachingIconView {
37     private NotificationIconProvider mIconProvider;
38 
39     private Drawable mAppIcon = null;
40 
41     // Padding, background and colors set on the view prior to being overridden when showing the app
42     // icon, to be restored if we're showing the small icon again.
43     private Rect mOriginalPadding = null;
44     private Drawable mOriginalBackground = null;
45     private int mOriginalBackgroundColor = ColoredIconHelper.COLOR_INVALID;
46     private int mOriginalIconColor = ColoredIconHelper.COLOR_INVALID;
47 
NotificationRowIconView(Context context)48     public NotificationRowIconView(Context context) {
49         super(context);
50     }
51 
NotificationRowIconView(Context context, @Nullable AttributeSet attrs)52     public NotificationRowIconView(Context context,
53             @Nullable AttributeSet attrs) {
54         super(context, attrs);
55     }
56 
NotificationRowIconView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)57     public NotificationRowIconView(Context context, @Nullable AttributeSet attrs,
58             int defStyleAttr) {
59         super(context, attrs, defStyleAttr);
60     }
61 
NotificationRowIconView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)62     public NotificationRowIconView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
63             int defStyleRes) {
64         super(context, attrs, defStyleAttr, defStyleRes);
65     }
66 
67     /**
68      * Sets the icon provider for this view. This is used to determine whether we should show the
69      * app icon instead of the small icon, and to fetch the app icon if needed.
70      */
setIconProvider(NotificationIconProvider iconProvider)71     public void setIconProvider(NotificationIconProvider iconProvider) {
72         mIconProvider = iconProvider;
73     }
74 
loadAppIcon()75     private Drawable loadAppIcon() {
76         if (mIconProvider != null && mIconProvider.shouldShowAppIcon()) {
77             return mIconProvider.getAppIcon();
78         }
79         return null;
80     }
81 
82     @RemotableViewMethod(asyncImpl = "setImageIconAsync")
83     @Override
setImageIcon(Icon icon)84     public void setImageIcon(Icon icon) {
85         if (Flags.notificationsRedesignAppIcons()) {
86             if (mAppIcon != null) {
87                 // We already know that we should be using the app icon, and we already loaded it.
88                 // We assume that cannot change throughout the lifetime of a notification, so
89                 // there's nothing to do here.
90                 return;
91             }
92             mAppIcon = loadAppIcon();
93             if (mAppIcon != null) {
94                 setImageDrawable(mAppIcon);
95                 adjustViewForAppIcon();
96             } else {
97                 super.setImageIcon(icon);
98                 restoreViewForSmallIcon();
99             }
100             return;
101         }
102         super.setImageIcon(icon);
103     }
104 
105     @RemotableViewMethod
106     @Override
setImageIconAsync(Icon icon)107     public Runnable setImageIconAsync(Icon icon) {
108         if (Flags.notificationsRedesignAppIcons()) {
109             if (mAppIcon != null) {
110                 // We already know that we should be using the app icon, and we already loaded it.
111                 // We assume that cannot change throughout the lifetime of a notification, so
112                 // there's nothing to do here.
113                 return () -> {
114                 };
115             }
116             mAppIcon = loadAppIcon();
117             if (mAppIcon != null) {
118                 return () -> {
119                     setImageDrawable(mAppIcon);
120                     adjustViewForAppIcon();
121                 };
122             } else {
123                 return () -> {
124                     super.setImageIcon(icon);
125                     restoreViewForSmallIcon();
126                 };
127             }
128         }
129         return super.setImageIconAsync(icon);
130     }
131 
132     /**
133      * Override padding and background from the view to display the app icon.
134      */
135     private void adjustViewForAppIcon() {
136         removePadding();
137         removeBackground();
138     }
139 
140     /**
141      * Restore padding and background overridden by {@link this#adjustViewForAppIcon}.
142      * Does nothing if they were not overridden.
143      */
144     private void restoreViewForSmallIcon() {
145         restorePadding();
146         restoreBackground();
147         restoreColors();
148     }
149 
150     private void removePadding() {
151         if (mOriginalPadding == null) {
152             mOriginalPadding = new Rect(getPaddingLeft(), getPaddingTop(),
153                     getPaddingRight(), getPaddingBottom());
154         }
155         setPadding(0, 0, 0, 0);
156     }
157 
158     private void restorePadding() {
159         if (mOriginalPadding != null) {
160             setPadding(mOriginalPadding.left, mOriginalPadding.top,
161                     mOriginalPadding.right,
162                     mOriginalPadding.bottom);
163             mOriginalPadding = null;
164         }
165     }
166 
167     private void removeBackground() {
168         if (mOriginalBackground == null) {
169             mOriginalBackground = getBackground();
170         }
171 
172         setBackground(null);
173     }
174 
175     private void restoreBackground() {
176         // NOTE: This will not work if the original background was null, but that's better than
177         //  accidentally clearing the background. We expect that there's generally going to be one
178         //  anyway unless we manually clear it.
179         if (mOriginalBackground != null) {
180             setBackground(mOriginalBackground);
181             mOriginalBackground = null;
182         }
183     }
184 
185     private void restoreColors() {
186         if (mOriginalBackgroundColor != ColoredIconHelper.COLOR_INVALID) {
187             super.setBackgroundColor(mOriginalBackgroundColor);
188             mOriginalBackgroundColor = ColoredIconHelper.COLOR_INVALID;
189         }
190         if (mOriginalIconColor != ColoredIconHelper.COLOR_INVALID) {
191             super.setOriginalIconColor(mOriginalIconColor);
192             mOriginalIconColor = ColoredIconHelper.COLOR_INVALID;
193         }
194     }
195 
196     @RemotableViewMethod
197     @Override
198     public void setBackgroundColor(int color) {
199         // Ignore color overrides if we're showing the app icon.
200         if (mAppIcon == null) {
201             super.setBackgroundColor(color);
202         } else {
203             mOriginalBackgroundColor = color;
204         }
205     }
206 
207     @RemotableViewMethod
208     @Override
209     public void setOriginalIconColor(int color) {
210         // Ignore color overrides if we're showing the app icon.
211         if (mAppIcon == null) {
212             super.setOriginalIconColor(color);
213         } else {
214             mOriginalIconColor = color;
215         }
216     }
217 
218     /**
219      * A provider that allows this view to verify whether it should use the app icon instead of the
220      * icon provided to it via setImageIcon, as well as actually fetching the app icon. It should
221      * primarily be called on the background thread.
222      */
223     public interface NotificationIconProvider {
224         /** Whether this notification should use the app icon instead of the small icon. */
225         boolean shouldShowAppIcon();
226 
227         /** Get the app icon for this notification. */
228         @Nullable Drawable getAppIcon();
229     }
230 }
231