Skip to content
Permalink
Browse files

Use a flag to grant a temporary URI permission.

It turns out that we can let the system to call
InputMethodService#exposeContent(InputContentInfo, EditorInfo), which
added in my previous CL [1], during the IME is calling
InputConnection#commitContent() as follows.

  [IME]
  InputContentInfo contentInfo = new InputContentInfo(
          contentUri,
          new ClipDescription(description, new String[]{mimeType}),
          linkUrl);
  getCurrentInputConnection().commitContent(
          inputContentInfo,
          InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION,
          null);

  [App]
  try {
      contentInfo.requestPermission();
      // Load inputContentInfo.getContentUri() here.
  } finally {
      contentInfo.releasePermission();
  }

This gives us flexibility to let InputConnection#commitContent() do all
the magic for IME developers like other APIs such as
Context#startActivity(), rather than asking them to call one more API to
grant a temporary URI permission like a scenario where
Context#grantUriPermission() is used.

 [1]: I2772889ca01f2ecb2cdeed4e04a9319bdf7bc5a6
      25e0813

Bug: 29450031
Change-Id: I99536cd58c9984af30b0bafb4a1dd25a26634a2d
  • Loading branch information...
yukawa committed Jun 24, 2016
1 parent 9ee8f80 commit 45700fa135e83ed44e4b69ca60cf12960a5898d7
public class InputMethodService extends android.inputmethodservice.AbstractInputMethodService {
ctor public InputMethodService();
method public deprecated boolean enableHardwareAcceleration();
method public final boolean exposeContent(android.view.inputmethod.InputContentInfo, android.view.inputmethod.EditorInfo);
method public int getBackDisposition();
method public int getCandidatesHiddenVisibility();
method public android.view.inputmethod.InputBinding getCurrentInputBinding();
method public boolean clearMetaKeyStates(int);
method public void closeConnection();
method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public boolean commitContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
method public boolean commitContent(android.view.inputmethod.InputContentInfo, int, android.os.Bundle);
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
method public boolean deleteSurroundingText(int, int);
method public abstract boolean clearMetaKeyStates(int);
method public abstract void closeConnection();
method public abstract boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public abstract boolean commitContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
method public abstract boolean commitContent(android.view.inputmethod.InputContentInfo, int, android.os.Bundle);
method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public abstract boolean commitText(java.lang.CharSequence, int);
method public abstract boolean deleteSurroundingText(int, int);
field public static final int CURSOR_UPDATE_MONITOR = 2; // 0x2
field public static final int GET_EXTRACTED_TEXT_MONITOR = 1; // 0x1
field public static final int GET_TEXT_WITH_STYLES = 1; // 0x1
field public static final int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = 1; // 0x1
}

public class InputConnectionWrapper implements android.view.inputmethod.InputConnection {
method public boolean clearMetaKeyStates(int);
method public void closeConnection();
method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public boolean commitContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
method public boolean commitContent(android.view.inputmethod.InputContentInfo, int, android.os.Bundle);
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
method public boolean deleteSurroundingText(int, int);
public class InputMethodService extends android.inputmethodservice.AbstractInputMethodService {
ctor public InputMethodService();
method public deprecated boolean enableHardwareAcceleration();
method public final boolean exposeContent(android.view.inputmethod.InputContentInfo, android.view.inputmethod.EditorInfo);
method public int getBackDisposition();
method public int getCandidatesHiddenVisibility();
method public android.view.inputmethod.InputBinding getCurrentInputBinding();
method public boolean clearMetaKeyStates(int);
method public void closeConnection();
method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public boolean commitContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
method public boolean commitContent(android.view.inputmethod.InputContentInfo, int, android.os.Bundle);
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
method public boolean deleteSurroundingText(int, int);
method public abstract boolean clearMetaKeyStates(int);
method public abstract void closeConnection();
method public abstract boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public abstract boolean commitContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
method public abstract boolean commitContent(android.view.inputmethod.InputContentInfo, int, android.os.Bundle);
method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public abstract boolean commitText(java.lang.CharSequence, int);
method public abstract boolean deleteSurroundingText(int, int);
field public static final int CURSOR_UPDATE_MONITOR = 2; // 0x2
field public static final int GET_EXTRACTED_TEXT_MONITOR = 1; // 0x1
field public static final int GET_TEXT_WITH_STYLES = 1; // 0x1
field public static final int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = 1; // 0x1
}

public class InputConnectionWrapper implements android.view.inputmethod.InputConnection {
method public boolean clearMetaKeyStates(int);
method public void closeConnection();
method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public boolean commitContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
method public boolean commitContent(android.view.inputmethod.InputContentInfo, int, android.os.Bundle);
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
method public boolean deleteSurroundingText(int, int);
public class InputMethodService extends android.inputmethodservice.AbstractInputMethodService {
ctor public InputMethodService();
method public deprecated boolean enableHardwareAcceleration();
method public final boolean exposeContent(android.view.inputmethod.InputContentInfo, android.view.inputmethod.EditorInfo);
method public int getBackDisposition();
method public int getCandidatesHiddenVisibility();
method public android.view.inputmethod.InputBinding getCurrentInputBinding();
method public boolean clearMetaKeyStates(int);
method public void closeConnection();
method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public boolean commitContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
method public boolean commitContent(android.view.inputmethod.InputContentInfo, int, android.os.Bundle);
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
method public boolean deleteSurroundingText(int, int);
method public abstract boolean clearMetaKeyStates(int);
method public abstract void closeConnection();
method public abstract boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public abstract boolean commitContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
method public abstract boolean commitContent(android.view.inputmethod.InputContentInfo, int, android.os.Bundle);
method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public abstract boolean commitText(java.lang.CharSequence, int);
method public abstract boolean deleteSurroundingText(int, int);
field public static final int CURSOR_UPDATE_MONITOR = 2; // 0x2
field public static final int GET_EXTRACTED_TEXT_MONITOR = 1; // 0x1
field public static final int GET_TEXT_WITH_STYLES = 1; // 0x1
field public static final int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = 1; // 0x1
}

public class InputConnectionWrapper implements android.view.inputmethod.InputConnection {
method public boolean clearMetaKeyStates(int);
method public void closeConnection();
method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public boolean commitContent(android.view.inputmethod.InputContentInfo, android.os.Bundle);
method public boolean commitContent(android.view.inputmethod.InputContentInfo, int, android.os.Bundle);
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
method public boolean deleteSurroundingText(int, int);
@@ -16,11 +16,14 @@

package android.inputmethodservice;

import android.annotation.NonNull;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;

@@ -208,7 +211,7 @@ final public IBinder onBind(Intent intent) {
*
* @param event The motion event being received.
* @return True if the event was handled in this function, false otherwise.
* @see View#onTrackballEvent
* @see android.view.View#onTrackballEvent(MotionEvent)
*/
public boolean onTrackballEvent(MotionEvent event) {
return false;
@@ -219,9 +222,30 @@ public boolean onTrackballEvent(MotionEvent event) {
*
* @param event The motion event being received.
* @return True if the event was handled in this function, false otherwise.
* @see View#onGenericMotionEvent
* @see android.view.View#onGenericMotionEvent(MotionEvent)
*/
public boolean onGenericMotionEvent(MotionEvent event) {
return false;
}

/**
* Allow the receiver of {@link InputContentInfo} to obtain a temporary read-only access
* permission to the content.
*
* <p>Default implementation does nothing.</p>
*
* @param inputContentInfo Content to be temporarily exposed from the input method to the
* application.
* This cannot be {@code null}.
* @param inputConnection {@link InputConnection} with which
* {@link InputConnection#commitContent(InputContentInfo, int, android.os.Bundle)} will be
* called.
* @return {@code false} if we cannot allow a temporary access permission.
* @hide
*/
public void exposeContent(@NonNull InputContentInfo inputContentInfo,
@NonNull InputConnection inputConnection) {
return;
}

}
@@ -168,7 +168,7 @@ public void executeMessage(Message msg) {
int missingMethods = msg.arg1;
IInputContext inputContext = (IInputContext)args.arg1;
InputConnection ic = inputContext != null
? new InputConnectionWrapper(inputContext, missingMethods) : null;
? new InputConnectionWrapper(mTarget, inputContext, missingMethods) : null;
EditorInfo info = (EditorInfo)args.arg2;
info.makeCompatible(mTargetSdkVersion);
inputMethod.startInput(ic, info);
@@ -180,7 +180,7 @@ public void executeMessage(Message msg) {
int missingMethods = msg.arg1;
IInputContext inputContext = (IInputContext)args.arg1;
InputConnection ic = inputContext != null
? new InputConnectionWrapper(inputContext, missingMethods) : null;
? new InputConnectionWrapper(mTarget, inputContext, missingMethods) : null;
EditorInfo info = (EditorInfo)args.arg2;
info.makeCompatible(mTargetSdkVersion);
inputMethod.restartInput(ic, info);
@@ -251,7 +251,7 @@ public void attachToken(IBinder token) {
public void bindInput(InputBinding binding) {
// This IInputContext is guaranteed to implement all the methods.
final int missingMethodFlags = 0;
InputConnection ic = new InputConnectionWrapper(
InputConnection ic = new InputConnectionWrapper(mTarget,
IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags);
InputBinding nu = new InputBinding(ic, binding);
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
@@ -2603,33 +2603,23 @@ public int getInputMethodWindowRecommendedHeight() {
* Allow the receiver of {@link InputContentInfo} to obtain a temporary read-only access
* permission to the content.
*
* <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 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
* to any other IME as long as the client keeps {@link InputContentInfo} object.</p>
*
* @param inputContentInfo Content to be temporarily exposed from the input method to the
* application.
* This cannot be {@code null}.
* @param editorInfo The editor that receives {@link InputContentInfo}.
* @return {@code false} if we cannot allow a temporary access permission.
* @param inputConnection {@link InputConnection} with which
* {@link InputConnection#commitContent(InputContentInfo, Bundle)} will be called.
* @hide
*/
public final boolean exposeContent(@NonNull InputContentInfo inputContentInfo,
@NonNull EditorInfo editorInfo) {
if (inputContentInfo == null) {
throw new NullPointerException("inputContentInfo");
@Override
public final void exposeContent(@NonNull InputContentInfo inputContentInfo,
@NonNull InputConnection inputConnection) {
if (inputConnection == null) {
return;
}
if (editorInfo == null) {
throw new NullPointerException("editorInfo");
if (getCurrentInputConnection() != inputConnection) {
return;
}

return mImm.exposeContent(mToken, inputContentInfo, editorInfo);
mImm.exposeContent(mToken, inputContentInfo, getCurrentInputEditorInfo());
}

/**
@@ -855,5 +855,7 @@ private void replaceText(CharSequence text, int newCursorPosition,
/**
* The default implementation does nothing.
*/
public boolean commitContent(InputContentInfo inputContentInfo, Bundle opts) { return false; }
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
return false;
}
}
@@ -368,10 +368,10 @@

/**
* List of acceptable MIME types for
* {@link InputConnection#commitContent(InputContentInfo, Bundle)}.
* {@link InputConnection#commitContent(InputContentInfo, int, Bundle)}.
*
* <p>{@code null} or an empty array means that
* {@link InputConnection#commitContent(InputContentInfo, Bundle)} is not supported in this
* {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} is not supported in this
* editor.</p>
*/
@Nullable
@@ -839,6 +839,24 @@ public ExtractedText getExtractedText(ExtractedTextRequest request,
*/
public void closeConnection();

/**
* 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 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
* to any other IME as long as the client keeps {@link InputContentInfo} object.</p>
**/
public static int INPUT_CONTENT_GRANT_READ_URI_PERMISSION =
android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; // 0x00000001

/**
* Called by the input method to commit a content such as PNG image to the editor.
*
@@ -862,9 +880,11 @@ public ExtractedText getExtractedText(ExtractedTextRequest request,
* </ul>
*
* @param inputContentInfo Content to be inserted.
* @param flags {@code 0} or {@link #INPUT_CONTENT_GRANT_READ_URI_PERMISSION}.
* @param opts optional bundle data. This can be {@code null}.
* @return {@code true} if this request is accepted by the application, no matter if the request
* is already handled or still being handled in background.
*/
public boolean commitContent(@NonNull InputContentInfo inputContentInfo, @Nullable Bundle opts);
public boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags,
@Nullable Bundle opts);
}
@@ -82,7 +82,7 @@
*/
int CLOSE_CONNECTION = 1 << 6;
/**
* {@link InputConnection#commitContent(InputContentInfo, Bundle)} is available in
* {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} is available in
* {@link android.os.Build.VERSION_CODES#N} MR-1 and later.
*/
int COMMIT_CONTENT = 1 << 7;
@@ -209,7 +209,7 @@ private static boolean hasCloseConnection(@NonNull final Class clazz) {
private static boolean hasCommitContent(@NonNull final Class clazz) {
try {
final Method method = clazz.getMethod("commitContent", InputContentInfo.class,
Bundle.class);
int.class, Bundle.class);
return !Modifier.isAbstract(method.getModifiers());
} catch (NoSuchMethodException e) {
return false;
@@ -274,7 +274,7 @@ public void closeConnection() {
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
public boolean commitContent(InputContentInfo inputContentInfo, Bundle opts) {
return mTarget.commitContent(inputContentInfo, opts);
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
return mTarget.commitContent(inputContentInfo, flags, opts);
}
}

0 comments on commit 45700fa

Please sign in to comment.
You can’t perform that action at this time.