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 java.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     private boolean mForceHidden;
45     private int mDesiredVisibility;
46 
CachingIconView(Context context, @Nullable AttributeSet attrs)47     public CachingIconView(Context context, @Nullable AttributeSet attrs) {
48         super(context, attrs);
49     }
50 
51     @Override
52     @RemotableViewMethod(asyncImpl="setImageIconAsync")
setImageIcon(@ullable Icon icon)53     public void setImageIcon(@Nullable Icon icon) {
54         if (!testAndSetCache(icon)) {
55             mInternalSetDrawable = true;
56             // This calls back to setImageDrawable, make sure we don't clear the cache there.
57             super.setImageIcon(icon);
58             mInternalSetDrawable = false;
59         }
60     }
61 
62     @Override
setImageIconAsync(@ullable Icon icon)63     public Runnable setImageIconAsync(@Nullable Icon icon) {
64         resetCache();
65         return super.setImageIconAsync(icon);
66     }
67 
68     @Override
69     @RemotableViewMethod(asyncImpl="setImageResourceAsync")
setImageResource(@rawableRes int resId)70     public void setImageResource(@DrawableRes int resId) {
71         if (!testAndSetCache(resId)) {
72             mInternalSetDrawable = true;
73             // This calls back to setImageDrawable, make sure we don't clear the cache there.
74             super.setImageResource(resId);
75             mInternalSetDrawable = false;
76         }
77     }
78 
79     @Override
setImageResourceAsync(@rawableRes int resId)80     public Runnable setImageResourceAsync(@DrawableRes int resId) {
81         resetCache();
82         return super.setImageResourceAsync(resId);
83     }
84 
85     @Override
86     @RemotableViewMethod(asyncImpl="setImageURIAsync")
setImageURI(@ullable Uri uri)87     public void setImageURI(@Nullable Uri uri) {
88         resetCache();
89         super.setImageURI(uri);
90     }
91 
92     @Override
setImageURIAsync(@ullable Uri uri)93     public Runnable setImageURIAsync(@Nullable Uri uri) {
94         resetCache();
95         return super.setImageURIAsync(uri);
96     }
97 
98     @Override
setImageDrawable(@ullable Drawable drawable)99     public void setImageDrawable(@Nullable Drawable drawable) {
100         if (!mInternalSetDrawable) {
101             // Only clear the cache if we were externally called.
102             resetCache();
103         }
104         super.setImageDrawable(drawable);
105     }
106 
107     @Override
108     @RemotableViewMethod
setImageBitmap(Bitmap bm)109     public void setImageBitmap(Bitmap bm) {
110         resetCache();
111         super.setImageBitmap(bm);
112     }
113 
114     @Override
onConfigurationChanged(Configuration newConfig)115     protected void onConfigurationChanged(Configuration newConfig) {
116         super.onConfigurationChanged(newConfig);
117         resetCache();
118     }
119 
120     /**
121      * @return true if the currently set image is the same as {@param icon}
122      */
testAndSetCache(Icon icon)123     private synchronized boolean testAndSetCache(Icon icon) {
124         if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
125             String iconPackage = normalizeIconPackage(icon);
126 
127             boolean isCached = mLastResId != 0
128                     && icon.getResId() == mLastResId
129                     && Objects.equals(iconPackage, mLastPackage);
130 
131             mLastPackage = iconPackage;
132             mLastResId = icon.getResId();
133 
134             return isCached;
135         } else {
136             resetCache();
137             return false;
138         }
139     }
140 
141     /**
142      * @return true if the currently set image is the same as {@param resId}
143      */
testAndSetCache(int resId)144     private synchronized boolean testAndSetCache(int resId) {
145         boolean isCached;
146         if (resId == 0 || mLastResId == 0) {
147             isCached = false;
148         } else {
149             isCached = resId == mLastResId && null == mLastPackage;
150         }
151         mLastPackage = null;
152         mLastResId = resId;
153         return isCached;
154     }
155 
156     /**
157      * Returns the normalized package name of {@param icon}.
158      * @return null if icon is null or if the icons package is null, empty or matches the current
159      *         context. Otherwise returns the icon's package context.
160      */
normalizeIconPackage(Icon icon)161     private String normalizeIconPackage(Icon icon) {
162         if (icon == null) {
163             return null;
164         }
165 
166         String pkg = icon.getResPackage();
167         if (TextUtils.isEmpty(pkg)) {
168             return null;
169         }
170         if (pkg.equals(mContext.getPackageName())) {
171             return null;
172         }
173         return pkg;
174     }
175 
resetCache()176     private synchronized void resetCache() {
177         mLastResId = 0;
178         mLastPackage = null;
179     }
180 
181     /**
182      * Set the icon to be forcibly hidden, even when it's visibility is changed to visible.
183      */
setForceHidden(boolean forceHidden)184     public void setForceHidden(boolean forceHidden) {
185         mForceHidden = forceHidden;
186         updateVisibility();
187     }
188 
189     @Override
190     @RemotableViewMethod
setVisibility(int visibility)191     public void setVisibility(int visibility) {
192         mDesiredVisibility = visibility;
193         updateVisibility();
194     }
195 
updateVisibility()196     private void updateVisibility() {
197         int visibility = mDesiredVisibility == VISIBLE && mForceHidden ? INVISIBLE
198                 : mDesiredVisibility;
199         super.setVisibility(visibility);
200     }
201 }
202