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

Commit 7e8449ca authored by Matt Casey's avatar Matt Casey
Browse files

Intercept chooser intents to use unbundled chooser (when enabled)

When SystemUiDeviceConfigFlags.USE_DELEGATE_CHOOSER is true (default
false), rewrite intents destined for the sytem chooser to go to the
unbundled chooser and attach a start activity permission token.

Outside of this CL we're working on removing the need for the permission
token. When unbundling is complete, there won't be any rewriting of
components, as the intent will already be destined for the unbundled chooser.
Assuming the token goes away, we could  then remove the interceptor
(b/222706900).

Bug: 202165481
Bug: 222706900
Test: atest ChooserActivityTest, manual tests with the flag enabled and
      not to verify that the proper sharesheet appears and functions.
Change-Id: I56e26157fc3e1b3f2b4dfeae4bb1ab555191e047
parent edc7b17c
Loading
Loading
Loading
Loading
+126 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.server.pm;

import static com.android.server.wm.ActivityInterceptorCallback.INTENT_RESOLVER_ORDERED_ID;

import android.Manifest;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityTaskManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.RemoteException;
import android.provider.DeviceConfig;
import android.util.Slog;

import com.android.internal.R;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.LocalServices;
import com.android.server.wm.ActivityInterceptorCallback;
import com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo;
import com.android.server.wm.ActivityTaskManagerInternal;

/**
 * Service to register an {@code ActivityInterceptorCallback} that modifies any {@code Intent}
 * that's being used to launch a user-space {@code ChooserActivity}, by adding
 * EXTRA_PERMISSION_TOKEN, a Binder representing a single-use-only permission to invoke the
 * #startActivityAsCaller() API (which normally isn't available in user-space); and setting the
 * destination component to the delegated component when appropriate.
 */
public final class IntentResolverInterceptor {
    private static final String TAG = "IntentResolverIntercept";

    private final Context mContext;
    private boolean mUseDelegateChooser;

    private final ActivityInterceptorCallback mActivityInterceptorCallback =
            new ActivityInterceptorCallback() {
                @Nullable
                @Override
                public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
                    if (mUseDelegateChooser && isChooserActivity(info)) {
                        return new ActivityInterceptResult(
                                modifyChooserIntent(info.intent),
                                info.checkedOptions);
                    }
                    return null;
                }
            };

    public IntentResolverInterceptor(Context context) {
        mContext = context;
    }

    /**
     * Start listening for intents and USE_DELEGATE_CHOOSER property changes.
     */
    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
    public void registerListeners() {
        LocalServices.getService(ActivityTaskManagerInternal.class)
                .registerActivityStartInterceptor(INTENT_RESOLVER_ORDERED_ID,
                        mActivityInterceptorCallback);

        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
                mContext.getMainExecutor(), properties -> updateUseDelegateChooser());
        updateUseDelegateChooser();
    }

    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
    private void updateUseDelegateChooser() {
        mUseDelegateChooser = DeviceConfig.getBoolean(
                DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.USE_DELEGATE_CHOOSER,
                false);
    }

    private Intent modifyChooserIntent(Intent intent) {
        intent.setComponent(getUnbundledChooserComponentName());
        addStartActivityPermissionTokenToIntent(intent, getUnbundledChooserComponentName());
        return intent;
    }

    private static boolean isChooserActivity(ActivityInterceptorInfo info) {
        ComponentName targetComponent = new ComponentName(info.aInfo.packageName, info.aInfo.name);

        return targetComponent.equals(getSystemChooserComponentName())
                || targetComponent.equals(getUnbundledChooserComponentName());
    }

    private static Intent addStartActivityPermissionTokenToIntent(
            Intent intent, ComponentName grantee) {
        try {
            intent.putExtra(
                    ActivityTaskManager.EXTRA_PERMISSION_TOKEN,
                    ActivityTaskManager.getService().requestStartActivityPermissionToken(grantee));
        } catch (RemoteException e) {
            Slog.w(TAG, "Failed to add permission token to chooser intent");
        }
        return intent;
    }

    private static ComponentName getSystemChooserComponentName() {
        return new ComponentName("android", "com.android.internal.app.ChooserActivity");
    }

    private static ComponentName getUnbundledChooserComponentName() {
        return ComponentName.unflattenFromString(
                Resources.getSystem().getString(R.string.config_chooserActivity));
    }
}
+10 −0
Original line number Diff line number Diff line
@@ -957,6 +957,7 @@ public class PackageManagerService extends IPackageManager.Stub
    private final ResolveIntentHelper mResolveIntentHelper;
    private final DexOptHelper mDexOptHelper;
    private final SuspendPackageHelper mSuspendPackageHelper;
    private final IntentResolverInterceptor mIntentResolverInterceptor;

    /**
     * Invalidate the package info cache, which includes updating the cached computer.
@@ -1710,6 +1711,8 @@ public class PackageManagerService extends IPackageManager.Stub

        mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);

        mIntentResolverInterceptor = null;

        registerObservers(false);
        invalidatePackageInfoCache();
    }
@@ -2238,6 +2241,8 @@ public class PackageManagerService extends IPackageManager.Stub

        mServiceStartWithDelay = SystemClock.uptimeMillis() + (60 * 1000L);

        mIntentResolverInterceptor = new IntentResolverInterceptor(mContext);

        Slog.i(TAG, "Fix for b/169414761 is applied");
    }

@@ -6395,6 +6400,11 @@ public class PackageManagerService extends IPackageManager.Stub

        // Prune unused static shared libraries which have been cached a period of time
        schedulePruneUnusedStaticSharedLibraries(false /* delay */);

        // TODO(b/222706900): Remove this intent interceptor before T launch
        if (mIntentResolverInterceptor != null) {
            mIntentResolverInterceptor.registerListeners();
        }
    }

    /**
+6 −0
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ public abstract class ActivityInterceptorCallback {
    @IntDef(suffix = { "_ORDERED_ID" }, value = {
            FIRST_ORDERED_ID,
            PERMISSION_POLICY_ORDERED_ID,
            INTENT_RESOLVER_ORDERED_ID,
            VIRTUAL_DEVICE_SERVICE_ORDERED_ID,
            LAST_ORDERED_ID // Update this when adding new ids
    })
@@ -74,6 +75,11 @@ public abstract class ActivityInterceptorCallback {
     */
    public static final int PERMISSION_POLICY_ORDERED_ID = 1;

    /**
     * The identifier for {@link com.android.server.pm.IntentResolverInterceptor}.
     */
    public static final int INTENT_RESOLVER_ORDERED_ID = 2;

    /**
     * The identifier for {@link com.android.server.companion.virtual.VirtualDeviceManagerService}
     * interceptor.