summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/tests/coretests/src/android/graphics/drawable/IconTest.java158
-rw-r--r--graphics/java/android/graphics/drawable/Icon.java46
2 files changed, 203 insertions, 1 deletions
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index 5d922961aa8b..49ed3a83e3d4 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -18,13 +18,20 @@ package android.graphics.drawable;
import static com.google.common.truth.Truth.assertThat;
+import android.app.IUriGrantsManager;
+import android.content.ContentProvider;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
import android.graphics.Region;
+import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.Parcel;
+import android.os.RemoteException;
import android.test.AndroidTestCase;
import android.util.Log;
@@ -34,6 +41,7 @@ import com.android.frameworks.coretests.R;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
@@ -457,6 +465,81 @@ public class IconTest extends AndroidTestCase {
assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
}
+ @SmallTest
+ public void testLoadSafeDrawable_loadSuccessful() throws FileNotFoundException {
+ int uid = 12345;
+ String packageName = "test_pkg";
+
+ final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
+ .getBitmap();
+ final File dir = getContext().getExternalFilesDir(null);
+ final File file1 = new File(dir, "file1-original.png");
+ bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1));
+
+ final Icon im1 = Icon.createWithFilePath(file1.toString());
+
+ TestableIUriGrantsManager ugm =
+ new TestableIUriGrantsManager(/* rejectCheckRequests */ false);
+
+ Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant(
+ getContext(), ugm, uid, packageName);
+ assertThat(loadedDrawable).isNotNull();
+
+ assertThat(ugm.mRequests.size()).isEqualTo(1);
+ TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0);
+ assertThat(r.mCallingUid).isEqualTo(uid);
+ assertThat(r.mPackageName).isEqualTo(packageName);
+ assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri()));
+ assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri()));
+
+ final Bitmap test1 = Bitmap.createBitmap(loadedDrawable.getIntrinsicWidth(),
+ loadedDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ loadedDrawable.setBounds(0, 0, loadedDrawable.getIntrinsicWidth(),
+ loadedDrawable.getIntrinsicHeight());
+ loadedDrawable.draw(new Canvas(test1));
+
+ bit1.compress(Bitmap.CompressFormat.PNG, 100,
+ new FileOutputStream(new File(dir, "bitmap1-original.png")));
+ test1.compress(Bitmap.CompressFormat.PNG, 100,
+ new FileOutputStream(new File(dir, "bitmap1-test.png")));
+ if (!equalBitmaps(bit1, test1)) {
+ findBitmapDifferences(bit1, test1);
+ fail("bitmap1 differs, check " + dir);
+ }
+ }
+
+ @SmallTest
+ public void testLoadSafeDrawable_grantRejected_nullDrawable() throws FileNotFoundException {
+ int uid = 12345;
+ String packageName = "test_pkg";
+
+ final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
+ .getBitmap();
+ final File dir = getContext().getExternalFilesDir(null);
+ final File file1 = new File(dir, "file1-original.png");
+ bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1));
+
+ final Icon im1 = Icon.createWithFilePath(file1.toString());
+
+ TestableIUriGrantsManager ugm =
+ new TestableIUriGrantsManager(/* rejectCheckRequests */ true);
+
+ Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant(
+ getContext(), ugm, uid, packageName);
+
+ assertThat(ugm.mRequests.size()).isEqualTo(1);
+ TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0);
+ assertThat(r.mCallingUid).isEqualTo(uid);
+ assertThat(r.mPackageName).isEqualTo(packageName);
+ assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri()));
+ assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri()));
+
+ assertThat(loadedDrawable).isNull();
+ }
+
+
// ======== utils ========
static final char[] GRADIENT = " .:;+=xX$#".toCharArray();
@@ -541,4 +624,77 @@ public class IconTest extends AndroidTestCase {
}
L(sb.toString());
}
-}
+
+ private static class TestableIUriGrantsManager extends IUriGrantsManager.Stub {
+
+ final ArrayList<CheckRequest> mRequests = new ArrayList<>();
+ final boolean mRejectCheckRequests;
+
+ TestableIUriGrantsManager(boolean rejectCheckRequests) {
+ this.mRejectCheckRequests = rejectCheckRequests;
+ }
+
+ @Override
+ public void takePersistableUriPermission(Uri uri, int i, String s, int i1)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void releasePersistableUriPermission(Uri uri, int i, String s, int i1)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void grantUriPermissionFromOwner(IBinder iBinder, int i, String s, Uri uri, int i1,
+ int i2, int i3) throws RemoteException {
+
+ }
+
+ @Override
+ public ParceledListSlice getGrantedUriPermissions(String s, int i) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void clearGrantedUriPermissions(String s, int i) throws RemoteException {
+
+ }
+
+ @Override
+ public ParceledListSlice getUriPermissions(String s, boolean b, boolean b1)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public int checkGrantUriPermission_ignoreNonSystem(
+ int uid, String packageName, Uri uri, int mode, int userId)
+ throws RemoteException {
+ CheckRequest r = new CheckRequest(uid, packageName, uri, mode, userId);
+ mRequests.add(r);
+ if (mRejectCheckRequests) {
+ throw new SecurityException();
+ } else {
+ return uid;
+ }
+ }
+
+ static class CheckRequest {
+ final int mCallingUid;
+ final String mPackageName;
+ final Uri mUri;
+ final int mMode;
+ final int mUserId;
+
+ CheckRequest(int callingUid, String packageName, Uri uri, int mode, int userId) {
+ this.mCallingUid = callingUid;
+ this.mPackageName = packageName;
+ this.mUri = uri;
+ this.mMode = mode;
+ this.mUserId = userId;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 708feeb9e421..5509f000aca5 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -24,9 +24,12 @@ import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.IUriGrantsManager;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
@@ -44,10 +47,13 @@ import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.RequiresPermission;
+
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
@@ -530,6 +536,46 @@ public final class Icon implements Parcelable {
return loadDrawable(context);
}
+ /**
+ * Load a drawable, but in the case of URI types, it will check if the passed uid has a grant
+ * to load the resource. The check will be performed using the permissions of the passed uid,
+ * and not those of the caller.
+ * <p>
+ * This should be called for {@link Icon} objects that come from a not trusted source and may
+ * contain a URI.
+ *
+ * After the check, if passed, {@link #loadDrawable} will be called. If failed, this will
+ * return {@code null}.
+ *
+ * @see #loadDrawable
+ *
+ * @hide
+ */
+ @Nullable
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ public Drawable loadDrawableCheckingUriGrant(
+ Context context,
+ IUriGrantsManager iugm,
+ int callingUid,
+ String packageName
+ ) {
+ if (getType() == TYPE_URI || getType() == TYPE_URI_ADAPTIVE_BITMAP) {
+ try {
+ iugm.checkGrantUriPermission_ignoreNonSystem(
+ callingUid,
+ packageName,
+ ContentProvider.getUriWithoutUserId(getUri()),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(getUri())
+ );
+ } catch (SecurityException | RemoteException e) {
+ Log.e(TAG, "Failed to get URI permission for: " + getUri(), e);
+ return null;
+ }
+ }
+ return loadDrawable(context);
+ }
+
/** @hide */
public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10);