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.content.Context; 22 import android.content.res.Configuration; 23 import android.graphics.Bitmap; 24 import android.graphics.drawable.Drawable; 25 import android.graphics.drawable.Icon; 26 import android.net.Uri; 27 import android.text.TextUtils; 28 import android.util.AttributeSet; 29 import android.view.RemotableViewMethod; 30 import android.widget.ImageView; 31 import android.widget.RemoteViews; 32 33 import libcore.util.Objects; 34 35 /** 36 * An ImageView for displaying an Icon. Avoids reloading the Icon when possible. 37 */ 38 @RemoteViews.RemoteView 39 public class CachingIconView extends ImageView { 40 41 private String mLastPackage; 42 private int mLastResId; 43 private boolean mInternalSetDrawable; 44 CachingIconView(Context context, @Nullable AttributeSet attrs)45 public CachingIconView(Context context, @Nullable AttributeSet attrs) { 46 super(context, attrs); 47 } 48 49 @Override 50 @RemotableViewMethod(asyncImpl="setImageIconAsync") setImageIcon(@ullable Icon icon)51 public void setImageIcon(@Nullable Icon icon) { 52 if (!testAndSetCache(icon)) { 53 mInternalSetDrawable = true; 54 // This calls back to setImageDrawable, make sure we don't clear the cache there. 55 super.setImageIcon(icon); 56 mInternalSetDrawable = false; 57 } 58 } 59 60 @Override setImageIconAsync(@ullable Icon icon)61 public Runnable setImageIconAsync(@Nullable Icon icon) { 62 resetCache(); 63 return super.setImageIconAsync(icon); 64 } 65 66 @Override 67 @RemotableViewMethod(asyncImpl="setImageResourceAsync") setImageResource(@rawableRes int resId)68 public void setImageResource(@DrawableRes int resId) { 69 if (!testAndSetCache(resId)) { 70 mInternalSetDrawable = true; 71 // This calls back to setImageDrawable, make sure we don't clear the cache there. 72 super.setImageResource(resId); 73 mInternalSetDrawable = false; 74 } 75 } 76 77 @Override setImageResourceAsync(@rawableRes int resId)78 public Runnable setImageResourceAsync(@DrawableRes int resId) { 79 resetCache(); 80 return super.setImageResourceAsync(resId); 81 } 82 83 @Override 84 @RemotableViewMethod(asyncImpl="setImageURIAsync") setImageURI(@ullable Uri uri)85 public void setImageURI(@Nullable Uri uri) { 86 resetCache(); 87 super.setImageURI(uri); 88 } 89 90 @Override setImageURIAsync(@ullable Uri uri)91 public Runnable setImageURIAsync(@Nullable Uri uri) { 92 resetCache(); 93 return super.setImageURIAsync(uri); 94 } 95 96 @Override setImageDrawable(@ullable Drawable drawable)97 public void setImageDrawable(@Nullable Drawable drawable) { 98 if (!mInternalSetDrawable) { 99 // Only clear the cache if we were externally called. 100 resetCache(); 101 } 102 super.setImageDrawable(drawable); 103 } 104 105 @Override 106 @RemotableViewMethod setImageBitmap(Bitmap bm)107 public void setImageBitmap(Bitmap bm) { 108 resetCache(); 109 super.setImageBitmap(bm); 110 } 111 112 @Override onConfigurationChanged(Configuration newConfig)113 protected void onConfigurationChanged(Configuration newConfig) { 114 super.onConfigurationChanged(newConfig); 115 resetCache(); 116 } 117 118 /** 119 * @return true if the currently set image is the same as {@param icon} 120 */ testAndSetCache(Icon icon)121 private synchronized boolean testAndSetCache(Icon icon) { 122 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 123 String iconPackage = normalizeIconPackage(icon); 124 125 boolean isCached = mLastResId != 0 126 && icon.getResId() == mLastResId 127 && Objects.equal(iconPackage, mLastPackage); 128 129 mLastPackage = iconPackage; 130 mLastResId = icon.getResId(); 131 132 return isCached; 133 } else { 134 resetCache(); 135 return false; 136 } 137 } 138 139 /** 140 * @return true if the currently set image is the same as {@param resId} 141 */ testAndSetCache(int resId)142 private synchronized boolean testAndSetCache(int resId) { 143 boolean isCached; 144 if (resId == 0 || mLastResId == 0) { 145 isCached = false; 146 } else { 147 isCached = resId == mLastResId && null == mLastPackage; 148 } 149 mLastPackage = null; 150 mLastResId = resId; 151 return isCached; 152 } 153 154 /** 155 * Returns the normalized package name of {@param icon}. 156 * @return null if icon is null or if the icons package is null, empty or matches the current 157 * context. Otherwise returns the icon's package context. 158 */ normalizeIconPackage(Icon icon)159 private String normalizeIconPackage(Icon icon) { 160 if (icon == null) { 161 return null; 162 } 163 164 String pkg = icon.getResPackage(); 165 if (TextUtils.isEmpty(pkg)) { 166 return null; 167 } 168 if (pkg.equals(mContext.getPackageName())) { 169 return null; 170 } 171 return pkg; 172 } 173 resetCache()174 private synchronized void resetCache() { 175 mLastResId = 0; 176 mLastPackage = null; 177 } 178 } 179