Revert "Remove InputContentInfo#requestPermission()"

This reverts commit c4b8f36de5523366e354fc01b6cba81ad72f6423.

Having InputContentInfo#requestPermission() should not hurt developers,
but we can polish the behavior in a subsequent CL without changing
the API.

Bug: 29450031
Bug: 29892936
Change-Id: I1b43c19417b643d0c269af860db2d309b73a90d5
diff --git a/api/current.txt b/api/current.txt
index a030416..c631627 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -44921,6 +44921,7 @@
     method public android.content.ClipDescription getDescription();
     method public android.net.Uri getLinkUri();
     method public void releasePermission();
+    method public void requestPermission();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.view.inputmethod.InputContentInfo> CREATOR;
   }
diff --git a/api/system-current.txt b/api/system-current.txt
index 1a20979..3fa518a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -48049,6 +48049,7 @@
     method public android.content.ClipDescription getDescription();
     method public android.net.Uri getLinkUri();
     method public void releasePermission();
+    method public void requestPermission();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.view.inputmethod.InputContentInfo> CREATOR;
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index b1e8fae..993f388 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -45001,6 +45001,7 @@
     method public android.content.ClipDescription getDescription();
     method public android.net.Uri getLinkUri();
     method public void releasePermission();
+    method public void requestPermission();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.view.inputmethod.InputContentInfo> CREATOR;
   }
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index ecfcbc5..07910b6 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -840,16 +840,15 @@
     public void closeConnection();
 
     /**
-     * When this flag is used in {@link #commitContent(InputContentInfo, int, Bundle)}, the editor
-     * will be able to request read access to the content URI contained in the
-     * {@link InputContentInfo} object.
+     * When this flag is used, the editor will be able to request read access to the content URI
+     * contained in the {@link InputContentInfo} object.
      *
      * <p>Make sure that the content provider owning the Uri sets the
      * {@link android.R.styleable#AndroidManifestProvider_grantUriPermissions
      * grantUriPermissions} attribute in its manifest or included the
      * {@link android.R.styleable#AndroidManifestGrantUriPermission
-     * &lt;grant-uri-permissions&gt;} tag. Otherwise
-     * {@link #commitContent(InputContentInfo, int, Bundle)} can fail.</p>
+     * &lt;grant-uri-permissions&gt;} tag. Otherwise {@link InputContentInfo#requestPermission()}
+     * can fail.</p>
      *
      * <p>Although calling this API is allowed only for the IME that is currently selected, the
      * client is able to request a temporary read-only access even after the current IME is switched
diff --git a/core/java/android/view/inputmethod/InputContentInfo.java b/core/java/android/view/inputmethod/InputContentInfo.java
index df20643..9579bbf 100644
--- a/core/java/android/view/inputmethod/InputContentInfo.java
+++ b/core/java/android/view/inputmethod/InputContentInfo.java
@@ -163,6 +163,22 @@
     }
 
     /**
+     * Requests a temporary read-only access permission for content URI associated with this object.
+     *
+     * <p>Does nothing if the temporary permission is already granted.</p>
+     */
+    public void requestPermission() {
+        if (mUriToken == null) {
+            return;
+        }
+        try {
+            mUriToken.take();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Releases a temporary read-only access permission for content URI associated with this object.
      *
      * <p>Does nothing if the temporary permission is not granted.</p>
diff --git a/core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl b/core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl
index c259348..8abc807 100644
--- a/core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl
@@ -22,5 +22,6 @@
  * {@hide}
  */
 interface IInputContentUriToken {
+    void take();
     void release();
 }
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 15422b6..9a09dcc 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -517,22 +517,20 @@
 
     public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
         boolean result = false;
-        final boolean grantUriPermission =
-                (flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0;
         if (isMethodMissing(MissingMethodFlags.COMMIT_CONTENT)) {
             // This method is not implemented.
             return false;
         }
         try {
-            if (grantUriPermission) {
+            if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
                 final AbstractInputMethodService inputMethodService = mInputMethodService.get();
                 if (inputMethodService == null) {
                     // This basically should not happen, because it's the the caller of this method.
                     return false;
                 }
-                // Temporarily grant URI permission.
                 inputMethodService.exposeContent(inputContentInfo, this);
             }
+
             InputContextCallback callback = InputContextCallback.getInstance();
             mIInputContext.commitContent(inputContentInfo, flags, opts, callback.mSeq, callback);
             synchronized (callback) {
@@ -542,10 +540,6 @@
                 }
             }
             callback.dispose();
-            // If this request is not handled, then there is no reason to keep the URI permission.
-            if (grantUriPermission && !result) {
-                inputContentInfo.releasePermission();
-            }
         } catch (RemoteException e) {
             return false;
         }
diff --git a/services/core/java/com/android/server/InputContentUriTokenHandler.java b/services/core/java/com/android/server/InputContentUriTokenHandler.java
index 12e1d62..3f4972b 100644
--- a/services/core/java/com/android/server/InputContentUriTokenHandler.java
+++ b/services/core/java/com/android/server/InputContentUriTokenHandler.java
@@ -45,40 +45,48 @@
     @GuardedBy("mLock")
     private IBinder mPermissionOwnerToken = null;
 
-    static InputContentUriTokenHandler create(@NonNull Uri contentUri, int sourceUid,
+    InputContentUriTokenHandler(@NonNull Uri contentUri, int sourceUid,
             @NonNull String targetPackage, @UserIdInt int sourceUserId,
             @UserIdInt int targetUserId) {
-        final IBinder permissionOwner;
-        try {
-            permissionOwner = ActivityManagerNative.getDefault()
-                    .newUriPermissionOwner("InputContentUriTokenHandler");
-        } catch (RemoteException e) {
-            return null;
-        }
-
-        long origId = Binder.clearCallingIdentity();
-        try {
-            ActivityManagerNative.getDefault().grantUriPermissionFromOwner(
-                    permissionOwner, sourceUserId, targetPackage, contentUri,
-                    Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, targetUserId);
-        } catch (RemoteException e) {
-            return null;
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-        return new InputContentUriTokenHandler(contentUri, sourceUid, targetPackage, sourceUserId,
-                targetUserId, permissionOwner);
-    }
-
-    private InputContentUriTokenHandler(@NonNull Uri contentUri, int sourceUid,
-            @NonNull String targetPackage, @UserIdInt int sourceUserId,
-            @UserIdInt int targetUserId, @NonNull IBinder permissionOwnerToken) {
         mUri = contentUri;
         mSourceUid = sourceUid;
         mTargetPackage = targetPackage;
         mSourceUserId = sourceUserId;
         mTargetUserId = targetUserId;
-        mPermissionOwnerToken = permissionOwnerToken;
+    }
+
+    @Override
+    public void take() {
+        synchronized (mLock) {
+            if (mPermissionOwnerToken != null) {
+                // Permission is already granted.
+                return;
+            }
+
+            try {
+                mPermissionOwnerToken = ActivityManagerNative.getDefault()
+                        .newUriPermissionOwner("InputContentUriTokenHandler");
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+
+            doTakeLocked(mPermissionOwnerToken);
+        }
+    }
+
+    private void doTakeLocked(@NonNull IBinder permissionOwner) {
+        long origId = Binder.clearCallingIdentity();
+        try {
+            try {
+                ActivityManagerNative.getDefault().grantUriPermissionFromOwner(
+                        permissionOwner, mSourceUid, mTargetPackage, mUri,
+                        Intent.FLAG_GRANT_READ_URI_PERMISSION, mSourceUserId, mTargetUserId);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 2c22829..e0d89f2 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -3953,7 +3953,7 @@
             }
             final int imeUserId = UserHandle.getUserId(uid);
             final int appUserId = UserHandle.getUserId(mCurClient.uid);
-            return InputContentUriTokenHandler.create(contentUri, uid, packageName, imeUserId,
+            return new InputContentUriTokenHandler(contentUri, uid, packageName, imeUserId,
                     appUserId);
         }
     }