Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fd173d1c authored by Fabian Kozynski's avatar Fabian Kozynski Committed by Android (Google) Code Review
Browse files

Merge changes I25e3d390,Iae392277 into main

* changes:
  Use new Icon API to restrict showing some drawables
  Add a new hidden Icon API for loading
parents dc588bc5 857ae16f
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -316,7 +316,14 @@ oneway interface IStatusBar
     */
    void requestTileServiceListeningState(in ComponentName componentName);

    void requestAddTile(in ComponentName componentName, in CharSequence appName, in CharSequence label, in Icon icon, in IAddTileResultCallback callback);
    void requestAddTile(
        int callingUid,
        in ComponentName componentName,
        in CharSequence appName,
        in CharSequence label,
        in Icon icon,
        in IAddTileResultCallback callback
    );
    void cancelRequestAddTile(in String packageName);

    /** Notifies System UI about an update to the media tap-to-transfer sender state. */
+157 −1
Original line number Diff line number Diff line
@@ -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
+46 −0
Original line number Diff line number Diff line
@@ -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);

+9 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.app.INotificationManager;
import android.app.IUriGrantsManager;
import android.app.IWallpaperManager;
import android.app.KeyguardManager;
import android.app.NotificationManager;
@@ -688,4 +689,12 @@ public class FrameworkServicesModule {
    static StatusBarManager provideStatusBarManager(Context context) {
        return context.getSystemService(StatusBarManager.class);
    }

    @Provides
    @Singleton
    static IUriGrantsManager provideIUriGrantsManager() {
        return IUriGrantsManager.Stub.asInterface(
                ServiceManager.getService(Context.URI_GRANTS_SERVICE)
        );
    }
}
+45 −104
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.systemui.qs.external;

import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;

import android.app.IUriGrantsManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -31,6 +32,7 @@ import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.provider.Settings;
import android.service.quicksettings.IQSTileService;
@@ -43,11 +45,9 @@ import android.view.View;
import android.view.WindowManagerGlobal;
import android.widget.Switch;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -67,9 +67,10 @@ import com.android.systemui.settings.DisplayTracker;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.inject.Inject;

import dagger.Lazy;
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;

public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
    public static final String PREFIX = "custom(";
@@ -109,24 +110,30 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
    private final AtomicBoolean mInitialDefaultIconFetched = new AtomicBoolean(false);
    private final TileServices mTileServices;

    private CustomTile(
            QSHost host,
    private int mServiceUid = Process.INVALID_UID;

    private final IUriGrantsManager mIUriGrantsManager;

    @AssistedInject
    CustomTile(
            Lazy<QSHost> host,
            QsEventLogger uiEventLogger,
            Looper backgroundLooper,
            Handler mainHandler,
            @Background Looper backgroundLooper,
            @Main Handler mainHandler,
            FalsingManager falsingManager,
            MetricsLogger metricsLogger,
            StatusBarStateController statusBarStateController,
            ActivityStarter activityStarter,
            QSLogger qsLogger,
            String action,
            Context userContext,
            @Assisted String action,
            @Assisted Context userContext,
            CustomTileStatePersister customTileStatePersister,
            TileServices tileServices,
            DisplayTracker displayTracker
            DisplayTracker displayTracker,
            IUriGrantsManager uriGrantsManager
    ) {
        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                statusBarStateController, activityStarter, qsLogger);
        super(host.get(), uiEventLogger, backgroundLooper, mainHandler, falsingManager,
                metricsLogger, statusBarStateController, activityStarter, qsLogger);
        mTileServices = tileServices;
        mWindowManager = WindowManagerGlobal.getWindowManagerService();
        mComponent = ComponentName.unflattenFromString(action);
@@ -139,6 +146,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
        mService = mServiceManager.getTileService();
        mCustomTileStatePersister = customTileStatePersister;
        mDisplayTracker = displayTracker;
        mIUriGrantsManager = uriGrantsManager;
    }

    @Override
@@ -268,7 +276,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
     *
     * @param tile tile populated with state to apply
     */
    public void updateTileState(Tile tile) {
    public void updateTileState(Tile tile, int appUid) {
        mServiceUid = appUid;
        // This comes from a binder call IQSService.updateQsTile
        mHandler.post(() -> handleUpdateTileState(tile));
    }
@@ -433,14 +442,25 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
        state.state = tileState;
        Drawable drawable = null;
        try {
            drawable = mTile.getIcon().loadDrawable(mUserContext);
            drawable = mTile.getIcon().loadDrawableCheckingUriGrant(
                    mUserContext,
                    mIUriGrantsManager,
                    mServiceUid,
                    mComponent.getPackageName()
            );
        } catch (Exception e) {
            Log.w(TAG, "Invalid icon, forcing into unavailable state");
            state.state = Tile.STATE_UNAVAILABLE;
            drawable = mDefaultIcon.loadDrawable(mUserContext);
        }

        final Drawable drawableF = drawable;
        final Drawable drawableF;
        if (drawable != null) {
            drawableF = drawable;
        } else if (mDefaultIcon != null) {
            drawableF = mDefaultIcon.loadDrawable(mUserContext);
        } else {
            drawableF = null;
        }
        state.iconSupplier = () -> {
            if (drawableF == null) return null;
            Drawable.ConstantState cs = drawableF.getConstantState();
@@ -543,96 +563,17 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
    /**
     * Create a {@link CustomTile} for a given spec and user.
     *
     * @param builder     including injected common dependencies.
     * @param factory     including injected common dependencies.
     * @param spec        as provided by {@link CustomTile#toSpec}
     * @param userContext context for the user that is creating this tile.
     * @return a new {@link CustomTile}
     */
    public static CustomTile create(Builder builder, String spec, Context userContext) {
        return builder
                .setSpec(spec)
                .setUserContext(userContext)
                .build();
    }

    public static class Builder {
        final Lazy<QSHost> mQSHostLazy;
        final QsEventLogger mUiEventLogger;
        final Looper mBackgroundLooper;
        final Handler mMainHandler;
        private final FalsingManager mFalsingManager;
        final MetricsLogger mMetricsLogger;
        final StatusBarStateController mStatusBarStateController;
        final ActivityStarter mActivityStarter;
        final QSLogger mQSLogger;
        final CustomTileStatePersister mCustomTileStatePersister;
        private TileServices mTileServices;
        final DisplayTracker mDisplayTracker;

        Context mUserContext;
        String mSpec = "";

        @Inject
        public Builder(
                Lazy<QSHost> hostLazy,
                QsEventLogger uiEventLogger,
                @Background Looper backgroundLooper,
                @Main Handler mainHandler,
                FalsingManager falsingManager,
                MetricsLogger metricsLogger,
                StatusBarStateController statusBarStateController,
                ActivityStarter activityStarter,
                QSLogger qsLogger,
                CustomTileStatePersister customTileStatePersister,
                TileServices tileServices,
                DisplayTracker displayTracker
        ) {
            mQSHostLazy = hostLazy;
            mUiEventLogger = uiEventLogger;
            mBackgroundLooper = backgroundLooper;
            mMainHandler = mainHandler;
            mFalsingManager = falsingManager;
            mMetricsLogger = metricsLogger;
            mStatusBarStateController = statusBarStateController;
            mActivityStarter = activityStarter;
            mQSLogger = qsLogger;
            mCustomTileStatePersister = customTileStatePersister;
            mTileServices = tileServices;
            mDisplayTracker = displayTracker;
        }

        Builder setSpec(@NonNull String spec) {
            mSpec = spec;
            return this;
    public static CustomTile create(Factory factory, String spec, Context userContext) {
        return factory.create(getAction(spec), userContext);
    }

        Builder setUserContext(@NonNull Context userContext) {
            mUserContext = userContext;
            return this;
        }

        @VisibleForTesting
        public CustomTile build() {
            if (mUserContext == null) {
                throw new NullPointerException("UserContext cannot be null");
            }
            String action = getAction(mSpec);
            return new CustomTile(
                    mQSHostLazy.get(),
                    mUiEventLogger,
                    mBackgroundLooper,
                    mMainHandler,
                    mFalsingManager,
                    mMetricsLogger,
                    mStatusBarStateController,
                    mActivityStarter,
                    mQSLogger,
                    action,
                    mUserContext,
                    mCustomTileStatePersister,
                    mTileServices,
                    mDisplayTracker
            );
        }
    @AssistedFactory
    public interface Factory {
        CustomTile create(String action, Context userContext);
    }
}
Loading