core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \
core/java/com/android/internal/backup/IBackupTransport.aidl \
core/java/com/android/internal/backup/IObbBackupService.aidl \
+ core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl \
core/java/com/android/internal/policy/IKeyguardDrawnCallback.aidl \
core/java/com/android/internal/policy/IKeyguardExitCallback.aidl \
core/java/com/android/internal/policy/IKeyguardService.aidl \
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 void setTarget(android.view.inputmethod.InputConnection);
}
- public class InputContentInfo implements android.os.Parcelable {
+ public final class InputContentInfo implements android.os.Parcelable {
ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription);
ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription, android.net.Uri);
method public int describeContents();
method public android.net.Uri getContentUri();
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;
}
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 void setTarget(android.view.inputmethod.InputConnection);
}
- public class InputContentInfo implements android.os.Parcelable {
+ public final class InputContentInfo implements android.os.Parcelable {
ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription);
ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription, android.net.Uri);
method public int describeContents();
method public android.net.Uri getContentUri();
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;
}
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 void setTarget(android.view.inputmethod.InputConnection);
}
- public class InputContentInfo implements android.os.Parcelable {
+ public final class InputContentInfo implements android.os.Parcelable {
ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription);
ctor public InputContentInfo(android.net.Uri, android.content.ClipDescription, android.net.Uri);
method public int describeContents();
method public android.net.Uri getContentUri();
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;
}
import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.MainThread;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.Dialog;
import android.content.Context;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
}
/**
+ * 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
+ * <grant-uri-permissions>} 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.
+ */
+ public final boolean exposeContent(@NonNull InputContentInfo inputContentInfo,
+ @NonNull EditorInfo editorInfo) {
+ if (inputContentInfo == null) {
+ throw new NullPointerException("inputContentInfo");
+ }
+ if (editorInfo == null) {
+ throw new NullPointerException("editorInfo");
+ }
+
+ return mImm.exposeContent(mToken, inputContentInfo, editorInfo);
+ }
+
+ /**
* Performs a dump of the InputMethodService's internal state. Override
* to add your own information to the dump.
*/
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
+
+import com.android.internal.inputmethod.IInputContentUriToken;
import java.security.InvalidParameterException;
/**
* A container object with which input methods can send content files to the target application.
*/
-public class InputContentInfo implements Parcelable {
+public final class InputContentInfo implements Parcelable {
@NonNull
private final Uri mContentUri;
private final ClipDescription mDescription;
@Nullable
private final Uri mLinkUri;
+ @NonNull
+ private IInputContentUriToken mUriToken;
/**
* Constructs {@link InputContentInfo} object only with mandatory data.
return false;
}
final String contentUriScheme = contentUri.getScheme();
- if (contentUriScheme == null || !contentUriScheme.equalsIgnoreCase("content")) {
+ if (!"content".equals(contentUriScheme)) {
if (throwException) {
throw new InvalidParameterException("contentUri must have content scheme");
}
public Uri getContentUri() { return mContentUri; }
/**
- * @return {@link ClipDescription} object that contains the metadata of {@code contentUri} such
- * as MIME type(s). {@link ClipDescription#getLabel()} can be used for accessibility purpose.
+ * @return {@link ClipDescription} object that contains the metadata of {@code #getContentUri()}
+ * such as MIME type(s). {@link ClipDescription#getLabel()} can be used for accessibility
+ * purpose.
*/
@NonNull
public ClipDescription getDescription() { return mDescription; }
@Nullable
public Uri getLinkUri() { return mLinkUri; }
+ void setUriToken(IInputContentUriToken token) {
+ if (mUriToken != null) {
+ throw new IllegalStateException("URI token is already set");
+ }
+ mUriToken = token;
+ }
+
+ /**
+ * 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>
+ */
+ public void releasePermission() {
+ if (mUriToken == null) {
+ return;
+ }
+ try {
+ mUriToken.release();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ } finally {
+ mUriToken = null;
+ }
+ }
+
/**
* Used to package this object into a {@link Parcel}.
*
Uri.writeToParcel(dest, mContentUri);
mDescription.writeToParcel(dest, flags);
Uri.writeToParcel(dest, mLinkUri);
+ if (mUriToken != null) {
+ dest.writeInt(1);
+ dest.writeStrongBinder(mUriToken.asBinder());
+ } else {
+ dest.writeInt(0);
+ }
}
private InputContentInfo(@NonNull Parcel source) {
mContentUri = Uri.CREATOR.createFromParcel(source);
mDescription = ClipDescription.CREATOR.createFromParcel(source);
mLinkUri = Uri.CREATOR.createFromParcel(source);
+ if (source.readInt() == 1) {
+ mUriToken = IInputContentUriToken.Stub.asInterface(source.readStrongBinder());
+ } else {
+ mUriToken = null;
+ }
}
/**
package android.view.inputmethod;
+import com.android.internal.inputmethod.IInputContentUriToken;
import com.android.internal.os.SomeArgs;
import com.android.internal.view.IInputConnectionWrapper;
import com.android.internal.view.IInputContext;
import android.annotation.RequiresPermission;
import android.content.Context;
import android.graphics.Rect;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
}
}
+ /**
+ * Allow the receiver of {@link InputContentInfo} to obtain a temporary read-only access
+ * permission to the content.
+ *
+ * <p>See {@link android.inputmethodservice.InputMethodService#exposeContent(InputContentInfo, EditorInfo)}
+ * for details.</p>
+ *
+ * @param token Supplies the identifying token given to an input method when it was started,
+ * which allows it to perform this operation on itself.
+ * @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.
+ * @hide
+ */
+ public boolean exposeContent(@NonNull IBinder token, @NonNull InputContentInfo inputContentInfo,
+ @NonNull EditorInfo editorInfo) {
+ final IInputContentUriToken uriToken;
+ final Uri contentUri = inputContentInfo.getContentUri();
+ try {
+ uriToken = mService.createInputContentUriToken(token, contentUri,
+ editorInfo.packageName);
+ if (uriToken == null) {
+ return false;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "createInputContentAccessToken failed. contentUri=" + contentUri.toString()
+ + " packageName=" + editorInfo.packageName, e);
+ return false;
+ }
+ inputContentInfo.setUriToken(uriToken);
+ return true;
+ }
+
void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
final Printer p = new PrintWriterPrinter(fout);
p.println("Input method client state for " + this + ":");
--- /dev/null
+/*
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package com.android.internal.inputmethod;
+
+import android.os.IBinder;
+
+/**
+ * {@hide}
+ */
+interface IInputContentUriToken {
+ void take();
+ void release();
+}
package com.android.internal.view;
+import android.net.Uri;
import android.os.ResultReceiver;
import android.text.style.SuggestionSpan;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.EditorInfo;
+import com.android.internal.inputmethod.IInputContentUriToken;
import com.android.internal.view.InputBindResult;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
int getInputMethodWindowVisibleHeight();
void clearLastInputMethodWindowForTransition(in IBinder token);
+ IInputContentUriToken createInputContentUriToken(in IBinder token, in Uri contentUri,
+ in String packageName);
+
oneway void notifyUserAction(int sequenceNumber);
}
--- /dev/null
+/*
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package com.android.server;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerNative;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IInputContentUriToken;
+
+final class InputContentUriTokenHandler extends IInputContentUriToken.Stub {
+
+ @NonNull
+ private final Uri mUri;
+ private final int mSourceUid;
+ @NonNull
+ private final String mTargetPackage;
+ @UserIdInt
+ private final int mSourceUserId;
+ @UserIdInt
+ private final int mTargetUserId;
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private IBinder mPermissionOwnerToken = null;
+
+ InputContentUriTokenHandler(@NonNull Uri contentUri, int sourceUid,
+ @NonNull String targetPackage, @UserIdInt int sourceUserId,
+ @UserIdInt int targetUserId) {
+ mUri = contentUri;
+ mSourceUid = sourceUid;
+ mTargetPackage = targetPackage;
+ mSourceUserId = sourceUserId;
+ mTargetUserId = targetUserId;
+ }
+
+ @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
+ public void release() {
+ synchronized (mLock) {
+ if (mPermissionOwnerToken == null) {
+ return;
+ }
+ try {
+ ActivityManagerNative.getDefault().revokeUriPermissionFromOwner(
+ mPermissionOwnerToken, mUri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION, mSourceUserId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ } finally {
+ mPermissionOwnerToken = null;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ release();
+ } finally {
+ super.finalize();
+ }
+ }
+}
import static java.lang.annotation.RetentionPolicy.SOURCE;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.inputmethod.IInputContentUriToken;
import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController;
import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
import com.android.internal.inputmethod.InputMethodUtils;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.nio.charset.StandardCharsets;
+import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
}
@Override
+ public IInputContentUriToken createInputContentUriToken(@Nullable IBinder token,
+ @Nullable Uri contentUri, @Nullable String packageName) {
+ if (!calledFromValidUser()) {
+ return null;
+ }
+
+ if (token == null) {
+ throw new NullPointerException("token");
+ }
+ if (packageName == null) {
+ throw new NullPointerException("packageName");
+ }
+ if (contentUri == null) {
+ throw new NullPointerException("contentUri");
+ }
+ final String contentUriScheme = contentUri.getScheme();
+ if (!"content".equals(contentUriScheme)) {
+ throw new InvalidParameterException("contentUri must have content scheme");
+ }
+
+ synchronized (mMethodMap) {
+ final int uid = Binder.getCallingUid();
+ if (mCurMethodId == null) {
+ return null;
+ }
+ if (mCurToken != token) {
+ Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + mCurToken
+ + " token=" + token);
+ return null;
+ }
+ // We cannot simply distinguish a bad IME that reports an arbitrary package name from
+ // an unfortunate IME whose internal state is already obsolete due to the asynchronous
+ // nature of our system. Let's compare it with our internal record.
+ if (!TextUtils.equals(mCurAttribute.packageName, packageName)) {
+ Slog.e(TAG, "Ignoring createInputContentUriToken mCurAttribute.packageName="
+ + mCurAttribute.packageName + " packageName=" + packageName);
+ return null;
+ }
+ final int imeUserId = UserHandle.getUserId(uid);
+ final int appUserId = UserHandle.getUserId(mCurClient.uid);
+ return new InputContentUriTokenHandler(contentUri, uid, packageName, imeUserId,
+ appUserId);
+ }
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
package com.android.layoutlib.bridge.android;
+import com.android.internal.inputmethod.IInputContentUriToken;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.InputBindResult;
+import android.net.Uri;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
// TODO Auto-generated method stub
return null;
}
+
+ @Override
+ public IInputContentUriToken createInputContentUriToken(IBinder token, Uri contentUri,
+ String packageName) {
+ // TODO Auto-generated method stub
+ return null;
+ }
}