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

Commit 16f25344 authored by Nikita Dubrovsky's avatar Nikita Dubrovsky Committed by Android (Google) Code Review
Browse files

Merge "Use ActivityRecord for URI permissions in autofill" into sc-dev

parents 05645200 b20628a5
Loading
Loading
Loading
Loading
+28 −157
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import static android.content.ContentResolver.SCHEME_CONTENT;

import static com.android.server.autofill.Helper.sVerbose;

import static java.lang.Integer.toHexString;

import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.IUriGrantsManager;
@@ -33,32 +35,16 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.uri.UriGrantsManagerInternal;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import com.android.server.wm.ActivityTaskManagerInternal;

/**
 * Grants and revokes URI permissions for content-based autofill suggestions.
 * Grants URI permissions for content-based autofill suggestions.
 *
 * <p>Note that the system cannot just hand out grants directly; it must always do so on behalf of
 * an owner (see {@link com.android.server.uri.UriGrantsManagerService}). For autofill, the owner
 * is the autofill service provider that creates a given autofill suggestion containing a content
 * URI. Therefore, this manager class must be instantiated with the service uid of the provider for
 * which it will manage URI grants.
 *
 * <p>To dump the state of this class, use {@code adb shell dumpsys autofill}.
 * <p>URI permissions granted by this class are tied to the activity being filled. When the
 * activity finishes, its URI grants are automatically revoked.
 *
 * <p>To dump all active URI permissions, use {@code adb shell dumpsys activity permissions}.
 */
@@ -69,26 +55,10 @@ final class AutofillUriGrantsManager {
    @UserIdInt
    private final int mSourceUserId;
    @NonNull
    private final IBinder mPermissionOwner;
    @NonNull
    private final UriGrantsManagerInternal mUgmInternal;
    private final ActivityTaskManagerInternal mActivityTaskMgrInternal;
    @NonNull
    private final IUriGrantsManager mUgm;

    // We use a local lock here for simplicity, since the synchronized code does not depend on
    // any other resources (the "hold and wait" condition required for deadlock is not present).
    // If this changes in the future, instead of using a local lock this should be updated to
    // use the shared lock from AutofillManagerServiceImpl.
    @NonNull
    private final Object mLock;

    // Tracks the URIs that have been granted to each package. For each URI, the map stores the
    // activities that triggered the grant. This allows revoking permissions only once all
    // activities that triggered the grant are finished.
    @NonNull
    @GuardedBy("mLock")
    private final ArrayMap<String, List<Pair<Uri, String>>> mActiveGrantsByPackage;

    /**
     * Creates a new instance of the manager.
     *
@@ -99,159 +69,60 @@ final class AutofillUriGrantsManager {
    AutofillUriGrantsManager(int serviceUid) {
        mSourceUid = serviceUid;
        mSourceUserId = UserHandle.getUserId(mSourceUid);
        mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
        mPermissionOwner = mUgmInternal.newUriPermissionOwner("autofill-" + serviceUid);
        mActivityTaskMgrInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
        mUgm = UriGrantsManager.getService();
        mLock = new Object();
        mActiveGrantsByPackage = new ArrayMap<>(0);
    }

    public void grantUriPermissions(@NonNull ComponentName targetActivity,
            @UserIdInt int targetUserId, @NonNull ClipData clip) {
        String targetPkg = targetActivity.getPackageName();
            @NonNull IBinder targetActivityToken, @UserIdInt int targetUserId,
            @NonNull ClipData clip) {
        final String targetPkg = targetActivity.getPackageName();
        final IBinder permissionOwner =
                mActivityTaskMgrInternal.getUriPermissionOwnerForActivity(targetActivityToken);
        if (permissionOwner == null) {
            Slog.w(TAG, "Can't grant URI permissions, because the target activity token is invalid:"
                    + " clip=" + clip
                    + ", targetActivity=" + targetActivity + ", targetUserId=" + targetUserId
                    + ", targetActivityToken=" + toHexString(targetActivityToken.hashCode()));
            return;
        }
        for (int i = 0; i < clip.getItemCount(); i++) {
            ClipData.Item item = clip.getItemAt(i);
            Uri uri = item.getUri();
            if (uri == null || !SCHEME_CONTENT.equals(uri.getScheme())) {
                continue;
            }
            if (grantUriPermissions(targetPkg, targetUserId, uri)) {
                addToActiveGrants(uri, targetActivity);
            }
        }
    }

    public void revokeUriPermissions(@NonNull ComponentName targetActivity,
            @UserIdInt int targetUserId) {
        String targetPkg = targetActivity.getPackageName();
        Set<Uri> urisWhoseGrantsShouldBeRevoked = removeFromActiveGrants(targetActivity);
        for (Uri uri : urisWhoseGrantsShouldBeRevoked) {
            revokeUriPermissions(targetPkg, targetUserId, uri);
            grantUriPermissions(uri, targetPkg, targetUserId, permissionOwner);
        }
    }

    private boolean grantUriPermissions(@NonNull String targetPkg, @UserIdInt int targetUserId,
            @NonNull Uri uri) {
    private void grantUriPermissions(@NonNull Uri uri, @NonNull String targetPkg,
            @UserIdInt int targetUserId, @NonNull IBinder permissionOwner) {
        final int sourceUserId = ContentProvider.getUserIdFromUri(uri, mSourceUserId);
        if (sVerbose) {
            Slog.v(TAG, "Granting URI permissions: uri=" + uri
                    + ", sourceUid=" + mSourceUid + ", sourceUserId=" + sourceUserId
                    + ", targetPkg=" + targetPkg + ", targetUserId=" + targetUserId);
                    + ", targetPkg=" + targetPkg + ", targetUserId=" + targetUserId
                    + ", permissionOwner=" + toHexString(permissionOwner.hashCode()));
        }
        final Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(uri);
        final long ident = Binder.clearCallingIdentity();
        try {
            mUgm.grantUriPermissionFromOwner(
                    mPermissionOwner,
                    permissionOwner,
                    mSourceUid,
                    targetPkg,
                    uriWithoutUserId,
                    Intent.FLAG_GRANT_READ_URI_PERMISSION,
                    sourceUserId,
                    targetUserId);
            return true;
        } catch (RemoteException e) {
            Slog.e(TAG, "Granting URI permissions failed: uri=" + uri
                    + ", sourceUid=" + mSourceUid + ", sourceUserId=" + sourceUserId
                    + ", targetPkg=" + targetPkg + ", targetUserId=" + targetUserId, e);
            return false;
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    private void revokeUriPermissions(@NonNull String targetPkg, @UserIdInt int targetUserId,
            @NonNull Uri uri) {
        final int sourceUserId = ContentProvider.getUserIdFromUri(uri, mSourceUserId);
        if (sVerbose) {
            Slog.v(TAG, "Revoking URI permissions: uri=" + uri
                    + ", sourceUid=" + mSourceUid + ", sourceUserId=" + sourceUserId
                    + ", target=" + targetPkg + ", targetUserId=" + targetUserId);
        }
        final Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(uri);
        final long ident = Binder.clearCallingIdentity();
        try {
            mUgmInternal.revokeUriPermissionFromOwner(
                    mPermissionOwner,
                    uriWithoutUserId,
                    Intent.FLAG_GRANT_READ_URI_PERMISSION,
                    sourceUserId,
                    targetPkg,
                    targetUserId);
                    + ", targetPkg=" + targetPkg + ", targetUserId=" + targetUserId
                    + ", permissionOwner=" + toHexString(permissionOwner.hashCode()), e);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    private void addToActiveGrants(@NonNull Uri uri, @NonNull ComponentName targetActivity) {
        synchronized (mLock) {
            String packageName = targetActivity.getPackageName();
            List<Pair<Uri, String>> uris = mActiveGrantsByPackage.computeIfAbsent(packageName,
                    k -> new ArrayList<>(1));
            uris.add(Pair.create(uri, targetActivity.getClassName()));
        }
    }

    private Set<Uri> removeFromActiveGrants(@NonNull ComponentName targetActivity) {
        synchronized (mLock) {
            String targetPackageName = targetActivity.getPackageName();
            List<Pair<Uri, String>> uris = mActiveGrantsByPackage.get(targetPackageName);
            if (uris == null || uris.isEmpty()) {
                return Collections.emptySet();
            }

            // Collect all URIs whose grant was triggered by the target activity.
            String targetActivityClassName = targetActivity.getClassName();
            Set<Uri> urisWhoseGrantsShouldBeRevoked = new ArraySet<>(1);
            for (Iterator<Pair<Uri, String>> iter = uris.iterator(); iter.hasNext(); ) {
                Pair<Uri, String> uriAndActivity = iter.next();
                if (uriAndActivity.second.equals(targetActivityClassName)) {
                    urisWhoseGrantsShouldBeRevoked.add(uriAndActivity.first);
                    iter.remove();
                }
            }

            // A URI grant may have been triggered by more than one activity for the same package.
            // We should not revoke a grant if it was triggered by multiple activities and one or
            // more of those activities is still alive. Therefore we do a second pass and prune
            // the set of URIs to be revoked if an additional activity that triggered its grant
            // is still present.
            for (Pair<Uri, String> uriAndActivity : uris) {
                urisWhoseGrantsShouldBeRevoked.remove(uriAndActivity.first);
            }

            // If there are no remaining URIs granted to the package, drop the entry from the map.
            if (uris.isEmpty()) {
                mActiveGrantsByPackage.remove(targetPackageName);
            }
            return urisWhoseGrantsShouldBeRevoked;
        }
    }

    /**
     * Dump the active URI grants.
     */
    public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
        synchronized (mLock) {
            if (mActiveGrantsByPackage.isEmpty()) {
                pw.print(prefix); pw.println("URI grants: none");
                return;
            }
            pw.print(prefix); pw.println("URI grants:");
            final String prefix2 = prefix + "  ";
            final String prefix3 = prefix2 + "  ";
            for (int i = mActiveGrantsByPackage.size() - 1; i >= 0; i--) {
                String packageName = mActiveGrantsByPackage.keyAt(i);
                pw.print(prefix2); pw.println(packageName);
                List<Pair<Uri, String>> uris = mActiveGrantsByPackage.valueAt(i);
                if (uris == null || uris.isEmpty())  {
                    continue;
                }
                for (Pair<Uri, String> uriAndActivity : uris) {
                    pw.print(prefix3);
                    pw.println(uriAndActivity.first + ": " + uriAndActivity.second);
                }
            }
        }
    }
}
+7 −13
Original line number Diff line number Diff line
@@ -57,7 +57,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.IResultReceiver;
import com.android.server.autofill.ui.InlineFillUi;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
@@ -152,8 +151,8 @@ final class RemoteAugmentedAutofillService
     * Called by {@link Session} to request augmented autofill.
     */
    public void onRequestAutofillLocked(int sessionId, @NonNull IAutoFillManagerClient client,
            int taskId, @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId,
            @Nullable AutofillValue focusedValue,
            int taskId, @NonNull ComponentName activityComponent, @NonNull IBinder activityToken,
            @NonNull AutofillId focusedId, @Nullable AutofillValue focusedValue,
            @Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
            @Nullable Function<InlineFillUi, Boolean> inlineSuggestionsCallback,
            @NonNull Runnable onErrorCallback,
@@ -181,7 +180,8 @@ final class RemoteAugmentedAutofillService
                                            inlineSuggestionsRequest, inlineSuggestionsData,
                                            clientState, focusedId, focusedValue,
                                            inlineSuggestionsCallback, client, onErrorCallback,
                                            remoteRenderService, userId, activityComponent);
                                            remoteRenderService, userId,
                                            activityComponent, activityToken);
                                    if (!showingFillWindow) {
                                        requestAutofill.complete(null);
                                    }
@@ -253,7 +253,7 @@ final class RemoteAugmentedAutofillService
            @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback,
            @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
            int userId,
            @NonNull ComponentName targetActivity) {
            @NonNull ComponentName targetActivity, @NonNull IBinder targetActivityToken) {
        if (inlineSuggestionsData == null || inlineSuggestionsData.isEmpty()
                || inlineSuggestionsCallback == null || request == null
                || remoteRenderService == null) {
@@ -307,8 +307,8 @@ final class RemoteAugmentedAutofillService
                                    final ArrayList<AutofillId> fieldIds = dataset.getFieldIds();
                                    final ClipData content = dataset.getFieldContent();
                                    if (content != null) {
                                        mUriGrantsManager.grantUriPermissions(
                                                targetActivity, userId, content);
                                        mUriGrantsManager.grantUriPermissions(targetActivity,
                                                targetActivityToken, userId, content);
                                        final AutofillId fieldId = fieldIds.get(0);
                                        if (sDebug) {
                                            Slog.d(TAG, "Calling client autofillContent(): "
@@ -368,12 +368,6 @@ final class RemoteAugmentedAutofillService
                + ComponentName.flattenToShortString(mComponentName) + "]";
    }

    @Override
    public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
        super.dump(prefix, pw);
        mUriGrantsManager.dump(prefix, pw);
    }

    /**
     * Called by {@link Session} when it's time to destroy all augmented autofill requests.
     */
+3 −9
Original line number Diff line number Diff line
@@ -1689,7 +1689,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        if (content != null) {
            final AutofillUriGrantsManager autofillUgm =
                    remoteAugmentedAutofillService.getAutofillUriGrantsManager();
            autofillUgm.grantUriPermissions(mComponentName, userId, content);
            autofillUgm.grantUriPermissions(mComponentName, mActivityToken, userId, content);
        }

        // Fill the value into the field.
@@ -3537,7 +3537,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
                    synchronized (mLock) {
                        logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
                                focusedId, isWhitelisted, inlineSuggestionsRequest != null);
                        remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName,
                        remoteService.onRequestAutofillLocked(id, mClient,
                                taskId, mComponentName, mActivityToken,
                                AutofillId.withoutSession(focusedId), currentValue,
                                inlineSuggestionsRequest, inlineSuggestionsResponseCallback,
                                /*onErrorCallback=*/ () -> {
@@ -4167,13 +4168,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
        if (remoteRenderService != null) {
            remoteRenderService.destroySuggestionViews(userId, id);
        }
        final RemoteAugmentedAutofillService remoteAugmentedAutofillService =
                mService.getRemoteAugmentedAutofillServiceIfCreatedLocked();
        if (remoteAugmentedAutofillService != null) {
            final AutofillUriGrantsManager autofillUgm =
                    remoteAugmentedAutofillService.getAutofillUriGrantsManager();
            autofillUgm.revokeUriPermissions(mComponentName, userId);
        }

        mDestroyed = true;

+8 −0
Original line number Diff line number Diff line
@@ -560,6 +560,14 @@ public abstract class ActivityTaskManagerInternal {

    public abstract ActivityMetricsLaunchObserverRegistry getLaunchObserverRegistry();

    /**
     * Returns the URI permission owner associated with the given activity (see
     * {@link ActivityRecord#getUriPermissionsLocked()}). If the passed-in activity token is
     * invalid, returns null.
     */
    @Nullable
    public abstract IBinder getUriPermissionOwnerForActivity(@NonNull IBinder activityToken);

    /**
     * Gets bitmap snapshot of the provided task id.
     *
+10 −0
Original line number Diff line number Diff line
@@ -6256,6 +6256,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
            }
        }

        @Nullable
        @Override
        public IBinder getUriPermissionOwnerForActivity(@NonNull IBinder activityToken) {
            ActivityTaskManagerService.enforceNotIsolatedCaller("getUriPermissionOwnerForActivity");
            synchronized (mGlobalLock) {
                ActivityRecord r = ActivityRecord.isInRootTaskLocked(activityToken);
                return (r == null) ? null : r.getUriPermissionsLocked().getExternalToken();
            }
        }

        @Override
        public TaskSnapshot getTaskSnapshotBlocking(
                int taskId, boolean isLowResolution) {