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

Commit 10646dff authored by Nikita Dubrovsky's avatar Nikita Dubrovsky
Browse files

OnReceiveContentListener: Handle IME insertion permissions release

For content insertion from the IME, the platform currently assumes
that it can automatically revoke URI permissions (via
InputContentUriTokenHandler.finalize) any time after the
InputContentInfo instance is garbage collected and
InputContentInfo.releasePermission() has not been called explicitly.
This can cause permissions to be revoked before the receiving code
has had a chance to read the content when using
OnReceiveContentListener, since we did not keep a reference to
InputContentInfo.

This change does the following:
* Sets the InputContentInfo instance in the ContentInfo payload passed
to OnReceiveContentListener.
* Exposes a hidden API on ContentInfo to allow system features to
proactively release permissions for sources that allow this (IME and
drag-and-drop).
* Updates notifications direct-reply code to proactively release
permissions as soon as they are no longer needed.

Bug: 183643556
Test: Manual
Change-Id: I1cbc55d53804810a3cd4b6b97c197f4dd2d7d8e6
parent 4b7b90e7
Loading
Loading
Loading
Loading
+53 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.content.ClipDescription;
import android.net.Uri;
import android.os.Bundle;
import android.util.Pair;
import android.view.inputmethod.InputContentInfo;

import com.android.internal.util.Preconditions;

@@ -141,6 +142,10 @@ public final class ContentInfo {
    private final Uri mLinkUri;
    @Nullable
    private final Bundle mExtras;
    @Nullable
    private final InputContentInfo mInputContentInfo;
    @Nullable
    private final DragAndDropPermissions mDragAndDropPermissions;

    private ContentInfo(Builder b) {
        this.mClip = Objects.requireNonNull(b.mClip);
@@ -149,6 +154,23 @@ public final class ContentInfo {
        this.mFlags = Preconditions.checkFlagsArgument(b.mFlags, FLAG_CONVERT_TO_PLAIN_TEXT);
        this.mLinkUri = b.mLinkUri;
        this.mExtras = b.mExtras;
        this.mInputContentInfo = b.mInputContentInfo;
        this.mDragAndDropPermissions = b.mDragAndDropPermissions;
    }

    /**
     * If the content came from a source that supports proactive release of URI permissions
     * (e.g. IME), releases permissions; otherwise a no-op.
     *
     * @hide
     */
    public void releasePermissions() {
        if (mInputContentInfo != null) {
            mInputContentInfo.releasePermission();
        }
        if (mDragAndDropPermissions != null) {
            mDragAndDropPermissions.release();
        }
    }

    @NonNull
@@ -275,6 +297,10 @@ public final class ContentInfo {
        private Uri mLinkUri;
        @Nullable
        private Bundle mExtras;
        @Nullable
        private InputContentInfo mInputContentInfo;
        @Nullable
        private DragAndDropPermissions mDragAndDropPermissions;

        /**
         * Creates a new builder initialized with the data from the given builder.
@@ -285,6 +311,8 @@ public final class ContentInfo {
            mFlags = other.mFlags;
            mLinkUri = other.mLinkUri;
            mExtras = other.mExtras;
            mInputContentInfo = other.mInputContentInfo;
            mDragAndDropPermissions = other.mDragAndDropPermissions;
        }

        /**
@@ -354,6 +382,31 @@ public final class ContentInfo {
            return this;
        }

        /**
         * Set the {@link InputContentInfo} object if the content is coming from the IME. This can
         * be used for proactive cleanup of permissions.
         *
         * @hide
         */
        @NonNull
        public Builder setInputContentInfo(@Nullable InputContentInfo inputContentInfo) {
            mInputContentInfo = inputContentInfo;
            return this;
        }

        /**
         * Set the {@link DragAndDropPermissions} object if the content is coming via drag-and-drop.
         * This can be used for proactive cleanup of permissions.
         *
         * @hide
         */
        @NonNull
        public Builder setDragAndDropPermissions(@Nullable DragAndDropPermissions permissions) {
            mDragAndDropPermissions = permissions;
            return this;
        }


        /**
         * @return A new {@link ContentInfo} instance with the data from this builder.
         */
+4 −2
Original line number Diff line number Diff line
@@ -26794,8 +26794,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            if (permissions != null) {
                permissions.takeTransient();
            }
            final ContentInfo payload = new ContentInfo.Builder(
                    event.getClipData(), SOURCE_DRAG_AND_DROP).build();
            final ContentInfo payload =
                    new ContentInfo.Builder(event.getClipData(), SOURCE_DRAG_AND_DROP)
                            .setDragAndDropPermissions(permissions)
                            .build();
            ContentInfo remainingPayload = performReceiveContent(payload);
            // Return true unless none of the payload was consumed.
            return remainingPayload != payload;
+2 −2
Original line number Diff line number Diff line
@@ -954,10 +954,10 @@ public class BaseInputConnection implements InputConnection {
        }
        final ClipData clip = new ClipData(inputContentInfo.getDescription(),
                new ClipData.Item(inputContentInfo.getContentUri()));
        final ContentInfo payload =
                new ContentInfo.Builder(clip, SOURCE_INPUT_METHOD)
        final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_INPUT_METHOD)
                .setLinkUri(inputContentInfo.getLinkUri())
                .setExtras(opts)
                .setInputContentInfo(inputContentInfo)
                .build();
        return mTargetView.performReceiveContent(payload) == null;
    }
+2 −2
Original line number Diff line number Diff line
@@ -2888,8 +2888,8 @@ public class Editor {
            final int originalLength = mTextView.getText().length();
            Selection.setSelection((Spannable) mTextView.getText(), offset);
            final ClipData clip = event.getClipData();
            final ContentInfo payload =
                    new ContentInfo.Builder(clip, SOURCE_DRAG_AND_DROP)
            final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_DRAG_AND_DROP)
                    .setDragAndDropPermissions(permissions)
                    .build();
            mTextView.performReceiveContent(payload);
            if (dragDropIntoItself) {
+6 −1
Original line number Diff line number Diff line
@@ -835,13 +835,18 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
                if (clip.getItemCount() > 1
                        || description.getMimeTypeCount() < 1
                        || remainingItems != null) {
                    // TODO(b/172363500): Update to loop over all the items
                    // Direct-reply in notifications currently only supports only one uri item
                    // at a time and requires the MIME type to be set.
                    Log.w(TAG, "Invalid payload: " + payload);
                    return payload;
                }
                Uri contentUri = clip.getItemAt(0).getUri();
                String mimeType = description.getMimeType(0);
                Intent dataIntent =
                        mRemoteInputView.prepareRemoteInputFromData(mimeType, contentUri);
                // We can release the uri permissions granted to us as soon as we've created the
                // grant for the target app in the call above.
                payload.releasePermissions();
                mRemoteInputView.sendRemoteInput(dataIntent);
            }
            return remainingItems;