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 17package com.android.internal.widget; 18 19import android.annotation.DrawableRes; 20import android.annotation.Nullable; 21import android.content.Context; 22import android.content.res.Configuration; 23import android.graphics.Bitmap; 24import android.graphics.drawable.Drawable; 25import android.graphics.drawable.Icon; 26import android.net.Uri; 27import android.text.TextUtils; 28import android.util.AttributeSet; 29import android.view.RemotableViewMethod; 30import android.widget.ImageView; 31import android.widget.RemoteViews; 32 33import libcore.util.Objects; 34 35/** 36 * An ImageView for displaying an Icon. Avoids reloading the Icon when possible. 37 */ 38@RemoteViews.RemoteView 39public class CachingIconView extends ImageView { 40 41 private String mLastPackage; 42 private int mLastResId; 43 private boolean mInternalSetDrawable; 44 45 public CachingIconView(Context context, @Nullable AttributeSet attrs) { 46 super(context, attrs); 47 } 48 49 @Override 50 @RemotableViewMethod(asyncImpl="setImageIconAsync") 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 61 public Runnable setImageIconAsync(@Nullable Icon icon) { 62 resetCache(); 63 return super.setImageIconAsync(icon); 64 } 65 66 @Override 67 @RemotableViewMethod(asyncImpl="setImageResourceAsync") 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 78 public Runnable setImageResourceAsync(@DrawableRes int resId) { 79 resetCache(); 80 return super.setImageResourceAsync(resId); 81 } 82 83 @Override 84 @RemotableViewMethod(asyncImpl="setImageURIAsync") 85 public void setImageURI(@Nullable Uri uri) { 86 resetCache(); 87 super.setImageURI(uri); 88 } 89 90 @Override 91 public Runnable setImageURIAsync(@Nullable Uri uri) { 92 resetCache(); 93 return super.setImageURIAsync(uri); 94 } 95 96 @Override 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 107 public void setImageBitmap(Bitmap bm) { 108 resetCache(); 109 super.setImageBitmap(bm); 110 } 111 112 @Override 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 */ 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 */ 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 */ 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 174 private synchronized void resetCache() { 175 mLastResId = 0; 176 mLastPackage = null; 177 } 178} 179