--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.DrawableRes;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
+import android.widget.ImageView;
+import android.widget.RemoteViews;
+
+import libcore.util.Objects;
+
+/**
+ * An ImageView for displaying an Icon. Avoids reloading the Icon when possible.
+ */
+@RemoteViews.RemoteView
+public class CachingIconView extends ImageView {
+
+ private String mLastPackage;
+ private int mLastResId;
+ private boolean mInternalSetDrawable;
+
+ public CachingIconView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ @RemotableViewMethod(asyncImpl="setImageIconAsync")
+ public void setImageIcon(@Nullable Icon icon) {
+ if (!testAndSetCache(icon)) {
+ mInternalSetDrawable = true;
+ // This calls back to setImageDrawable, make sure we don't clear the cache there.
+ super.setImageIcon(icon);
+ mInternalSetDrawable = false;
+ }
+ }
+
+ @Override
+ public Runnable setImageIconAsync(@Nullable Icon icon) {
+ resetCache();
+ return super.setImageIconAsync(icon);
+ }
+
+ @Override
+ @RemotableViewMethod(asyncImpl="setImageResourceAsync")
+ public void setImageResource(@DrawableRes int resId) {
+ if (!testAndSetCache(resId)) {
+ mInternalSetDrawable = true;
+ // This calls back to setImageDrawable, make sure we don't clear the cache there.
+ super.setImageResource(resId);
+ mInternalSetDrawable = false;
+ }
+ }
+
+ @Override
+ public Runnable setImageResourceAsync(@DrawableRes int resId) {
+ resetCache();
+ return super.setImageResourceAsync(resId);
+ }
+
+ @Override
+ @RemotableViewMethod(asyncImpl="setImageURIAsync")
+ public void setImageURI(@Nullable Uri uri) {
+ resetCache();
+ super.setImageURI(uri);
+ }
+
+ @Override
+ public Runnable setImageURIAsync(@Nullable Uri uri) {
+ resetCache();
+ return super.setImageURIAsync(uri);
+ }
+
+ @Override
+ public void setImageDrawable(@Nullable Drawable drawable) {
+ if (!mInternalSetDrawable) {
+ // Only clear the cache if we were externally called.
+ resetCache();
+ }
+ super.setImageDrawable(drawable);
+ }
+
+ @Override
+ @RemotableViewMethod
+ public void setImageBitmap(Bitmap bm) {
+ resetCache();
+ super.setImageBitmap(bm);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ resetCache();
+ }
+
+ /**
+ * @return true if the currently set image is the same as {@param icon}
+ */
+ private synchronized boolean testAndSetCache(Icon icon) {
+ if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
+ String iconPackage = normalizeIconPackage(icon);
+
+ boolean isCached = mLastResId != 0
+ && icon.getResId() == mLastResId
+ && Objects.equal(iconPackage, mLastPackage);
+
+ mLastPackage = iconPackage;
+ mLastResId = icon.getResId();
+
+ return isCached;
+ } else {
+ resetCache();
+ return false;
+ }
+ }
+
+ /**
+ * @return true if the currently set image is the same as {@param resId}
+ */
+ private synchronized boolean testAndSetCache(int resId) {
+ boolean isCached;
+ if (resId == 0 || mLastResId == 0) {
+ isCached = false;
+ } else {
+ isCached = resId == mLastResId && null == mLastPackage;
+ }
+ mLastPackage = null;
+ mLastResId = resId;
+ return isCached;
+ }
+
+ /**
+ * Returns the normalized package name of {@param icon}.
+ * @return null if icon is null or if the icons package is null, empty or matches the current
+ * context. Otherwise returns the icon's package context.
+ */
+ private String normalizeIconPackage(Icon icon) {
+ if (icon == null) {
+ return null;
+ }
+
+ String pkg = icon.getResPackage();
+ if (TextUtils.isEmpty(pkg)) {
+ return null;
+ }
+ if (pkg.equals(mContext.getPackageName())) {
+ return null;
+ }
+ return pkg;
+ }
+
+ private synchronized void resetCache() {
+ mLastResId = 0;
+ mLastPackage = null;
+ }
+}