1 /* 2 * Copyright (C) 2016 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.DrawableRes; 20 import android.annotation.Nullable; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.content.Context; 23 import android.content.res.Configuration; 24 import android.graphics.Bitmap; 25 import android.graphics.drawable.Drawable; 26 import android.graphics.drawable.Icon; 27 import android.net.Uri; 28 import android.text.TextUtils; 29 import android.util.AttributeSet; 30 import android.view.RemotableViewMethod; 31 import android.widget.ImageView; 32 import android.widget.RemoteViews; 33 34 import java.util.Objects; 35 import java.util.function.Consumer; 36 37 /** 38 * An ImageView for displaying an Icon. Avoids reloading the Icon when possible. 39 */ 40 @RemoteViews.RemoteView 41 public class CachingIconView extends ImageView { 42 43 private String mLastPackage; 44 private int mLastResId; 45 private boolean mInternalSetDrawable; 46 private boolean mForceHidden; 47 private int mDesiredVisibility; 48 private Consumer<Integer> mOnVisibilityChangedListener; 49 private Consumer<Boolean> mOnForceHiddenChangedListener; 50 private int mIconColor; 51 private boolean mWillBeForceHidden; 52 53 @UnsupportedAppUsage CachingIconView(Context context, @Nullable AttributeSet attrs)54 public CachingIconView(Context context, @Nullable AttributeSet attrs) { 55 super(context, attrs); 56 } 57 58 @Override 59 @RemotableViewMethod(asyncImpl="setImageIconAsync") setImageIcon(@ullable Icon icon)60 public void setImageIcon(@Nullable Icon icon) { 61 if (!testAndSetCache(icon)) { 62 mInternalSetDrawable = true; 63 // This calls back to setImageDrawable, make sure we don't clear the cache there. 64 super.setImageIcon(icon); 65 mInternalSetDrawable = false; 66 } 67 } 68 69 @Override setImageIconAsync(@ullable Icon icon)70 public Runnable setImageIconAsync(@Nullable Icon icon) { 71 resetCache(); 72 return super.setImageIconAsync(icon); 73 } 74 75 @Override 76 @RemotableViewMethod(asyncImpl="setImageResourceAsync") setImageResource(@rawableRes int resId)77 public void setImageResource(@DrawableRes int resId) { 78 if (!testAndSetCache(resId)) { 79 mInternalSetDrawable = true; 80 // This calls back to setImageDrawable, make sure we don't clear the cache there. 81 super.setImageResource(resId); 82 mInternalSetDrawable = false; 83 } 84 } 85 86 @Override setImageResourceAsync(@rawableRes int resId)87 public Runnable setImageResourceAsync(@DrawableRes int resId) { 88 resetCache(); 89 return super.setImageResourceAsync(resId); 90 } 91 92 @Override 93 @RemotableViewMethod(asyncImpl="setImageURIAsync") setImageURI(@ullable Uri uri)94 public void setImageURI(@Nullable Uri uri) { 95 resetCache(); 96 super.setImageURI(uri); 97 } 98 99 @Override setImageURIAsync(@ullable Uri uri)100 public Runnable setImageURIAsync(@Nullable Uri uri) { 101 resetCache(); 102 return super.setImageURIAsync(uri); 103 } 104 105 @Override setImageDrawable(@ullable Drawable drawable)106 public void setImageDrawable(@Nullable Drawable drawable) { 107 if (!mInternalSetDrawable) { 108 // Only clear the cache if we were externally called. 109 resetCache(); 110 } 111 super.setImageDrawable(drawable); 112 } 113 114 @Override 115 @RemotableViewMethod setImageBitmap(Bitmap bm)116 public void setImageBitmap(Bitmap bm) { 117 resetCache(); 118 super.setImageBitmap(bm); 119 } 120 121 @Override onConfigurationChanged(Configuration newConfig)122 protected void onConfigurationChanged(Configuration newConfig) { 123 super.onConfigurationChanged(newConfig); 124 resetCache(); 125 } 126 127 /** 128 * @return true if the currently set image is the same as {@param icon} 129 */ testAndSetCache(Icon icon)130 private synchronized boolean testAndSetCache(Icon icon) { 131 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 132 String iconPackage = normalizeIconPackage(icon); 133 134 boolean isCached = mLastResId != 0 135 && icon.getResId() == mLastResId 136 && Objects.equals(iconPackage, mLastPackage); 137 138 mLastPackage = iconPackage; 139 mLastResId = icon.getResId(); 140 141 return isCached; 142 } else { 143 resetCache(); 144 return false; 145 } 146 } 147 148 /** 149 * @return true if the currently set image is the same as {@param resId} 150 */ testAndSetCache(int resId)151 private synchronized boolean testAndSetCache(int resId) { 152 boolean isCached; 153 if (resId == 0 || mLastResId == 0) { 154 isCached = false; 155 } else { 156 isCached = resId == mLastResId && null == mLastPackage; 157 } 158 mLastPackage = null; 159 mLastResId = resId; 160 return isCached; 161 } 162 163 /** 164 * Returns the normalized package name of {@param icon}. 165 * @return null if icon is null or if the icons package is null, empty or matches the current 166 * context. Otherwise returns the icon's package context. 167 */ normalizeIconPackage(Icon icon)168 private String normalizeIconPackage(Icon icon) { 169 if (icon == null) { 170 return null; 171 } 172 173 String pkg = icon.getResPackage(); 174 if (TextUtils.isEmpty(pkg)) { 175 return null; 176 } 177 if (pkg.equals(mContext.getPackageName())) { 178 return null; 179 } 180 return pkg; 181 } 182 resetCache()183 private synchronized void resetCache() { 184 mLastResId = 0; 185 mLastPackage = null; 186 } 187 188 /** 189 * Set the icon to be forcibly hidden, even when it's visibility is changed to visible. 190 * This is necessary since we still want to keep certain views hidden when their visibility 191 * is modified from other sources like the shelf. 192 */ setForceHidden(boolean forceHidden)193 public void setForceHidden(boolean forceHidden) { 194 if (forceHidden != mForceHidden) { 195 mForceHidden = forceHidden; 196 mWillBeForceHidden = false; 197 updateVisibility(); 198 if (mOnForceHiddenChangedListener != null) { 199 mOnForceHiddenChangedListener.accept(forceHidden); 200 } 201 } 202 } 203 204 @Override 205 @RemotableViewMethod setVisibility(int visibility)206 public void setVisibility(int visibility) { 207 mDesiredVisibility = visibility; 208 updateVisibility(); 209 } 210 updateVisibility()211 private void updateVisibility() { 212 int visibility = mDesiredVisibility == VISIBLE && mForceHidden ? INVISIBLE 213 : mDesiredVisibility; 214 if (mOnVisibilityChangedListener != null) { 215 mOnVisibilityChangedListener.accept(visibility); 216 } 217 super.setVisibility(visibility); 218 } 219 setOnVisibilityChangedListener(Consumer<Integer> listener)220 public void setOnVisibilityChangedListener(Consumer<Integer> listener) { 221 mOnVisibilityChangedListener = listener; 222 } 223 setOnForceHiddenChangedListener(Consumer<Boolean> listener)224 public void setOnForceHiddenChangedListener(Consumer<Boolean> listener) { 225 mOnForceHiddenChangedListener = listener; 226 } 227 228 isForceHidden()229 public boolean isForceHidden() { 230 return mForceHidden; 231 } 232 233 @RemotableViewMethod setOriginalIconColor(int color)234 public void setOriginalIconColor(int color) { 235 mIconColor = color; 236 } 237 getOriginalIconColor()238 public int getOriginalIconColor() { 239 return mIconColor; 240 } 241 242 /** 243 * @return if the view will be forceHidden after an animation 244 */ willBeForceHidden()245 public boolean willBeForceHidden() { 246 return mWillBeForceHidden; 247 } 248 249 /** 250 * Set that this view will be force hidden after an animation 251 * 252 * @param forceHidden if it will be forcehidden 253 */ setWillBeForceHidden(boolean forceHidden)254 public void setWillBeForceHidden(boolean forceHidden) { 255 mWillBeForceHidden = forceHidden; 256 } 257 } 258