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