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

Commit f333d26f authored by Nikita Dubrovsky's avatar Nikita Dubrovsky
Browse files

Release drag-and-drop URI permissions on gc if not tied to an activity

The implementation for drag-and-drop provides two ways of taking URI
permissions: a public API that ties permissions to an activity and
an internal API that grants "transient" permissions. The latter is
used in a single code path: dropping content into an editable
TextView (implemented in android.widget.Editor).

The transient code path was previously (Android R and below)
implemented using a try/finally block in Editor.onDrop. The change
I7ae38069fb741925211b42c7ff28784eb9722ce3 had updated that logic to
tie permissions to the app process lifecycle instead, to account for
the new OnReceiveContentListener which apps can use to implement
custom drop handling (and use a background thread to do the processing
of the content). The problem with having permissions tied to the app
process lifecycle is that the app process could potentially be
long-running.

This change updates the logic to tie transient permissions to the
DragAndDropPermissions object lifecycle (release whenever there are no
more reference to the object and it is garbage collected). This
follows the same approach as used for IME permission grants
(InputContentInfo.java and InputContentUriTokenHandler.java).

Bug: 181178998
Test: atest CtsWindowManagerDeviceTestCases:CrossAppDragAndDropTests
Test: Manually verified using development/samples/ReceiveContentDemo
Change-Id: I950d4572ee19af64da1ace1572fe12e17d09c609
parent ac3b7aaf
Loading
Loading
Loading
Loading
+5 −26
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package android.view;
import static java.lang.Integer.toHexString;

import android.app.Activity;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -62,24 +61,6 @@ public final class DragAndDropPermissions implements Parcelable {
    private static final String TAG = "DragAndDrop";
    private static final boolean DEBUG = false;

    /**
     * Permissions for a drop can be granted in one of two ways:
     * <ol>
     *     <li>An app can explicitly request permissions using
     *     {@link Activity#requestDragAndDropPermissions(DragEvent)}. In this case permissions are
     *     revoked automatically when then activity is destroyed. See {@link #take(IBinder)}.
     *     <li>The platform can request permissions on behalf of the app (e.g. in
     *     {@link android.widget.Editor}). In this case permissions are revoked automatically when
     *     the app process terminates. See {@link #takeTransient()}.
     * </ol>
     *
     * <p>In order to implement the second case above, we create a static token object here. This
     * ensures that the token stays alive for the lifetime of the app process, allowing us to
     * revoke permissions when the app process terminates using {@link IBinder#linkToDeath} in
     * {@code DragAndDropPermissionsHandler}.
     */
    private static IBinder sAppToken;

    private final IDragAndDropPermissions mDragAndDropPermissions;

    /**
@@ -128,7 +109,9 @@ public final class DragAndDropPermissions implements Parcelable {
    }

    /**
     * Take permissions transiently. Permissions will be revoked when the app process terminates.
     * Take permissions transiently. Permissions will be tied to this object's lifecycle; if not
     * released explicitly, they will be released automatically when there are no more references
     * to this object and it's garbage collected.
     *
     * <p>Note: This API is not exposed to apps.
     *
@@ -138,14 +121,10 @@ public final class DragAndDropPermissions implements Parcelable {
     */
    public boolean takeTransient() {
        try {
            if (sAppToken == null) {
                sAppToken = new Binder();
            }
            if (DEBUG) {
                Log.d(TAG, this + ": calling takeTransient() with process-bound token: "
                        + toHexString(sAppToken.hashCode()));
                Log.d(TAG, this + ": calling takeTransient()");
            }
            mDragAndDropPermissions.takeTransient(sAppToken);
            mDragAndDropPermissions.takeTransient();
        } catch (RemoteException e) {
            Log.w(TAG, this + ": takeTransient() failed with a RemoteException", e);
            return false;
+20 −1
Original line number Diff line number Diff line
@@ -74,7 +74,7 @@ public interface OnReceiveContentListener {
     * preserve the common behavior for inserting text. See the class javadoc for a sample
     * implementation.
     *
     * <p>Handling different content
     * <h3>Handling different content</h3>
     * <ul>
     *     <li>Text. If the {@link ContentInfo#getSource() source} is
     *     {@link ContentInfo#SOURCE_AUTOFILL autofill}, the view's content should be fully
@@ -86,6 +86,25 @@ public interface OnReceiveContentListener {
     *     completely separate view).
     * </ul>
     *
     * <h3>URI permissions</h3>
     * <p>{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION Read permissions} are
     * granted automatically by the platform for any
     * {@link android.content.ContentResolver#SCHEME_CONTENT content URIs} in the payload passed
     * to this listener. Permissions are transient and will be released automatically by the
     * platform.
     * <ul>
     *     <li>If the {@link ContentInfo#getSource() source} is the
     *     {@link ContentInfo#SOURCE_CLIPBOARD clipboard}, permissions are released whenever the
     *     next copy action is performed by the user.
     *     <li>If the source is {@link ContentInfo#SOURCE_AUTOFILL autofill}, permissions are tied
     *     to the target {@link android.app.Activity} lifecycle (released when the activity
     *     finishes).
     *     <li>For other sources, permissions are tied to the passed-in {@code payload} object
     *     (released automatically when there are no more references to it). To ensure that
     *     permissions are not released prematurely, implementations of this listener should pass
     *     along the {@code payload} object if processing is done on a background thread.
     * </ul>
     *
     * @param view The view where the content insertion was requested.
     * @param payload The content to insert and related metadata.
     *
+6 −1
Original line number Diff line number Diff line
@@ -199,7 +199,12 @@ public final class InputContentInfo implements Parcelable {
    }

    /**
     * Requests a temporary read-only access permission for content URI associated with this object.
     * Requests a temporary {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION read-only}
     * access permission for the content URI associated with this object.
     *
     * <p>The lifecycle of the permission granted here is tied to this object instance. If the
     * permission is not released explicitly via {@link #releasePermission()}, it will be
     * released automatically when there are no more references to this object.</p>
     *
     * <p>Does nothing if the temporary permission is already granted.</p>
     */
+1 −1
Original line number Diff line number Diff line
@@ -24,6 +24,6 @@ import android.os.IBinder;
 */
interface IDragAndDropPermissions {
    void take(IBinder activityToken);
    void takeTransient(IBinder transientToken);
    void takeTransient();
    void release();
}
+2 −1
Original line number Diff line number Diff line
@@ -104,7 +104,8 @@ final class InputContentUriTokenHandler extends IInputContentUriToken.Stub {
    }

    /**
     * {@inheritDoc}
     * If permissions are not released explicitly via {@link #release()}, release automatically
     * whenever there are no more references to this object.
     */
    @Override
    protected void finalize() throws Throwable {
Loading