Prevent loading of Icon resources from the wrong package

If an Icon carries package designator, LocalImageResolver has now
ignored it. This could lead to loading of a wrong icon in cases where
the ID collided with the same ID in android package.

This is fixes that corner-case.

Bug: 241066484
Test: atest LocalImageResolverTest - the new test checks for pkg
      correctness and fails on devices without this cl

      manual test: bluejay device with known, reproducible resource
      collision in media player notification. In reproducible case,
      icon appears as a broken block.

      After applying this patch, small icon in media notification loads
      correctly for the collision case.

Change-Id: Ic79c0d4acb1d347a24282972e009a5a079ce6c21
This commit is contained in:
Jernej Virag
2022-08-12 18:06:23 +02:00
parent 7b43124412
commit 166d420cf4
2 changed files with 108 additions and 4 deletions

View File

@@ -19,6 +19,8 @@ package com.android.internal.widget;
import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.ImageDecoder;
@@ -109,13 +111,13 @@ public class LocalImageResolver {
}
break;
case Icon.TYPE_RESOURCE:
if (!(TextUtils.isEmpty(icon.getResPackage())
|| context.getPackageName().equals(icon.getResPackage()))) {
// We can't properly resolve icons from other packages here, so fall back.
Resources res = resolveResourcesForIcon(context, icon);
if (res == null) {
// We couldn't resolve resources properly, fall back to icon loading.
return icon.loadDrawable(context);
}
Drawable result = resolveImage(icon.getResId(), context, maxWidth, maxHeight);
Drawable result = resolveImage(res, icon.getResId(), maxWidth, maxHeight);
if (result != null) {
return tintDrawable(icon, result);
}
@@ -158,6 +160,13 @@ public class LocalImageResolver {
return resolveImage(source, maxWidth, maxHeight);
}
@Nullable
private static Drawable resolveImage(Resources res, @DrawableRes int resId, int maxWidth,
int maxHeight) {
final ImageDecoder.Source source = ImageDecoder.createSource(res, resId);
return resolveImage(source, maxWidth, maxHeight);
}
@Nullable
private static Drawable resolveBitmapImage(Icon icon, Context context, int maxWidth,
int maxHeight) {
@@ -259,4 +268,52 @@ public class LocalImageResolver {
}
return icon.getUri();
}
/**
* Resolves the correct resources package for a given Icon - it may come from another
* package.
*
* @see Icon#loadDrawableInner(Context)
* @hide
*
* @return resources instance if the operation succeeded, null otherwise
*/
@Nullable
@VisibleForTesting
public static Resources resolveResourcesForIcon(Context context, Icon icon) {
if (icon.getType() != Icon.TYPE_RESOURCE) {
return null;
}
// Icons cache resolved resources, use cache if available.
Resources res = icon.getResources();
if (res != null) {
return res;
}
String resPackage = icon.getResPackage();
// No package means we try to use current context.
if (TextUtils.isEmpty(resPackage) || context.getPackageName().equals(resPackage)) {
return context.getResources();
}
if ("android".equals(resPackage)) {
return Resources.getSystem();
}
final PackageManager pm = context.getPackageManager();
try {
ApplicationInfo ai = pm.getApplicationInfo(resPackage,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.GET_SHARED_LIBRARY_FILES);
if (ai != null) {
return pm.getResourcesForApplication(ai);
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, String.format("Unable to resolve package %s for icon %s", resPackage, icon));
return null;
}
return null;
}
}

View File

@@ -17,6 +17,8 @@
package com.android.internal.widget;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
@@ -279,4 +281,49 @@ public class LocalImageResolverTest {
// This drawable must not be loaded - if it was, the code ignored the package specification.
assertThat(d).isNull();
}
@Test
public void resolveResourcesForIcon_notAResourceIcon_returnsNull() {
Icon icon = Icon.createWithContentUri(Uri.parse("some_uri"));
assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon)).isNull();
}
@Test
public void resolveResourcesForIcon_localPackageIcon_returnsPackageResources() {
Icon icon = Icon.createWithResource(mContext, R.drawable.test32x24);
assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon))
.isSameInstanceAs(mContext.getResources());
}
@Test
public void resolveResourcesForIcon_iconWithoutPackageSpecificed_returnsPackageResources() {
Icon icon = Icon.createWithResource("", R.drawable.test32x24);
assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon))
.isSameInstanceAs(mContext.getResources());
}
@Test
public void resolveResourcesForIcon_systemPackageSpecified_returnsSystemPackage() {
Icon icon = Icon.createWithResource("android", R.drawable.test32x24);
assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon)).isSameInstanceAs(
Resources.getSystem());
}
@Test
public void resolveResourcesForIcon_differentPackageSpecified_returnsPackageResources() throws
PackageManager.NameNotFoundException {
String pkg = "com.android.settings";
Resources res = mContext.getPackageManager().getResourcesForApplication(pkg);
int resId = res.getIdentifier("ic_android", "drawable", pkg);
Icon icon = Icon.createWithResource(pkg, resId);
assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon).getDrawable(resId,
mContext.getTheme())).isNotNull();
}
@Test
public void resolveResourcesForIcon_invalidPackageSpecified_returnsNull() {
Icon icon = Icon.createWithResource("invalid.package", R.drawable.test32x24);
assertThat(LocalImageResolver.resolveResourcesForIcon(mContext, icon)).isNull();
}
}