Merge "Change input injection security model to require INJECT_EVENTS permission" into tm-dev am: 0df8e9cedd
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17125748 Change-Id: Ic2f3285cf46bdab97f9a8cdf4b1e76a08709794f Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -562,14 +562,15 @@ public abstract class ActivityManagerInternal {
|
|||||||
public abstract void unregisterProcessObserver(IProcessObserver processObserver);
|
public abstract void unregisterProcessObserver(IProcessObserver processObserver);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if there is an unfinished instrumentation that targets the given uid.
|
* Gets the uid of the instrumentation source if there is an unfinished instrumentation that
|
||||||
|
* targets the given uid.
|
||||||
*
|
*
|
||||||
* @param uid The uid to be checked for
|
* @param uid The uid to be checked for
|
||||||
*
|
*
|
||||||
* @return True, if there is an instrumentation whose target application uid matches the given
|
* @return the uid of the instrumentation source, if there is an instrumentation whose target
|
||||||
* uid, false otherwise
|
* application uid matches the given uid, and {@link android.os.Process#INVALID_UID} otherwise.
|
||||||
*/
|
*/
|
||||||
public abstract boolean isUidCurrentlyInstrumented(int uid);
|
public abstract int getInstrumentationSourceUid(int uid);
|
||||||
|
|
||||||
/** Is this a device owner app? */
|
/** Is this a device owner app? */
|
||||||
public abstract boolean isDeviceOwner(int uid);
|
public abstract boolean isDeviceOwner(int uid);
|
||||||
|
|||||||
@@ -1058,10 +1058,11 @@ public class Instrumentation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the key events corresponding to the text to the app being
|
* Sends the key events that result in the given text being typed into the currently focused
|
||||||
* instrumented.
|
* window, and waits for it to be processed.
|
||||||
*
|
*
|
||||||
* @param text The text to be sent.
|
* @param text The text to be sent.
|
||||||
|
* @see #sendKeySync(KeyEvent)
|
||||||
*/
|
*/
|
||||||
public void sendStringSync(String text) {
|
public void sendStringSync(String text) {
|
||||||
if (text == null) {
|
if (text == null) {
|
||||||
@@ -1084,11 +1085,12 @@ public class Instrumentation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a key event to the currently focused window/view and wait for it to
|
* Sends a key event to the currently focused window, and waits for it to be processed.
|
||||||
* be processed. Finished at some point after the recipient has returned
|
* <p>
|
||||||
* from its event processing, though it may <em>not</em> have completely
|
* This method blocks until the recipient has finished handling the event. Note that the
|
||||||
* finished reacting from the event -- for example, if it needs to update
|
* recipient may <em>not</em> have completely finished reacting from the event when this method
|
||||||
* its display as a result, it may still be in the process of doing that.
|
* returns. For example, it may still be in the process of updating its display or UI contents
|
||||||
|
* upon reacting to the injected event.
|
||||||
*
|
*
|
||||||
* @param event The event to send to the current focus.
|
* @param event The event to send to the current focus.
|
||||||
*/
|
*/
|
||||||
@@ -1116,34 +1118,42 @@ public class Instrumentation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an up and down key event sync to the currently focused window.
|
* Sends up and down key events with the given key code to the currently focused window, and
|
||||||
|
* waits for it to be processed.
|
||||||
*
|
*
|
||||||
* @param key The integer keycode for the event.
|
* @param keyCode The key code for the events to send.
|
||||||
|
* @see #sendKeySync(KeyEvent)
|
||||||
*/
|
*/
|
||||||
public void sendKeyDownUpSync(int key) {
|
public void sendKeyDownUpSync(int keyCode) {
|
||||||
sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, key));
|
|
||||||
sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, key));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Higher-level method for sending both the down and up key events for a
|
|
||||||
* particular character key code. Equivalent to creating both KeyEvent
|
|
||||||
* objects by hand and calling {@link #sendKeySync}. The event appears
|
|
||||||
* as if it came from keyboard 0, the built in one.
|
|
||||||
*
|
|
||||||
* @param keyCode The key code of the character to send.
|
|
||||||
*/
|
|
||||||
public void sendCharacterSync(int keyCode) {
|
|
||||||
sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
|
sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
|
||||||
sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch a pointer event. Finished at some point after the recipient has
|
* Sends up and down key events with the given key code to the currently focused window, and
|
||||||
* returned from its event processing, though it may <em>not</em> have
|
* waits for it to be processed.
|
||||||
* completely finished reacting from the event -- for example, if it needs
|
* <p>
|
||||||
* to update its display as a result, it may still be in the process of
|
* Equivalent to {@link #sendKeyDownUpSync(int)}.
|
||||||
* doing that.
|
*
|
||||||
|
* @param keyCode The key code of the character to send.
|
||||||
|
* @see #sendKeySync(KeyEvent)
|
||||||
|
*/
|
||||||
|
public void sendCharacterSync(int keyCode) {
|
||||||
|
sendKeyDownUpSync(keyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a pointer event into a window owned by the instrumented application, and waits for
|
||||||
|
* it to be processed.
|
||||||
|
* <p>
|
||||||
|
* If the motion event being injected is targeted at a window that is not owned by the
|
||||||
|
* instrumented application, the input injection will fail. See {@link #getUiAutomation()} for
|
||||||
|
* injecting events into all windows.
|
||||||
|
* <p>
|
||||||
|
* This method blocks until the recipient has finished handling the event. Note that the
|
||||||
|
* recipient may <em>not</em> have completely finished reacting from the event when this method
|
||||||
|
* returns. For example, it may still be in the process of updating its display or UI contents
|
||||||
|
* upon reacting to the injected event.
|
||||||
*
|
*
|
||||||
* @param event A motion event describing the pointer action. (As noted in
|
* @param event A motion event describing the pointer action. (As noted in
|
||||||
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
|
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
|
||||||
@@ -1155,10 +1165,10 @@ public class Instrumentation {
|
|||||||
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
|
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
syncInputTransactionsAndInjectEvent(event);
|
syncInputTransactionsAndInjectEventIntoSelf(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void syncInputTransactionsAndInjectEvent(MotionEvent event) {
|
private void syncInputTransactionsAndInjectEventIntoSelf(MotionEvent event) {
|
||||||
final boolean syncBefore = event.getAction() == MotionEvent.ACTION_DOWN
|
final boolean syncBefore = event.getAction() == MotionEvent.ACTION_DOWN
|
||||||
|| event.isFromSource(InputDevice.SOURCE_MOUSE);
|
|| event.isFromSource(InputDevice.SOURCE_MOUSE);
|
||||||
final boolean syncAfter = event.getAction() == MotionEvent.ACTION_UP;
|
final boolean syncAfter = event.getAction() == MotionEvent.ACTION_UP;
|
||||||
@@ -1169,8 +1179,9 @@ public class Instrumentation {
|
|||||||
.syncInputTransactions(true /*waitForAnimations*/);
|
.syncInputTransactions(true /*waitForAnimations*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Direct the injected event into windows owned by the instrumentation target.
|
||||||
InputManager.getInstance().injectInputEvent(
|
InputManager.getInstance().injectInputEvent(
|
||||||
event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT);
|
event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT, Process.myUid());
|
||||||
|
|
||||||
if (syncAfter) {
|
if (syncAfter) {
|
||||||
WindowManagerGlobal.getWindowManagerService()
|
WindowManagerGlobal.getWindowManagerService()
|
||||||
@@ -1182,19 +1193,26 @@ public class Instrumentation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch a trackball event. Finished at some point after the recipient has
|
* Dispatches a trackball event into the currently focused window, and waits for it to be
|
||||||
* returned from its event processing, though it may <em>not</em> have
|
* processed.
|
||||||
* completely finished reacting from the event -- for example, if it needs
|
* <p>
|
||||||
* to update its display as a result, it may still be in the process of
|
* This method blocks until the recipient has finished handling the event. Note that the
|
||||||
* doing that.
|
* recipient may <em>not</em> have completely finished reacting from the event when this method
|
||||||
*
|
* returns. For example, it may still be in the process of updating its display or UI contents
|
||||||
|
* upon reacting to the injected event.
|
||||||
|
*
|
||||||
* @param event A motion event describing the trackball action. (As noted in
|
* @param event A motion event describing the trackball action. (As noted in
|
||||||
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
|
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
|
||||||
* {@link SystemClock#uptimeMillis()} as the timebase.
|
* {@link SystemClock#uptimeMillis()} as the timebase.
|
||||||
*/
|
*/
|
||||||
public void sendTrackballEventSync(MotionEvent event) {
|
public void sendTrackballEventSync(MotionEvent event) {
|
||||||
validateNotAppThread();
|
validateNotAppThread();
|
||||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) {
|
if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Cannot inject pointer events from sendTrackballEventSync()."
|
||||||
|
+ " Use sendPointerSync() to inject pointer events.");
|
||||||
|
}
|
||||||
|
if (!event.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
|
||||||
event.setSource(InputDevice.SOURCE_TRACKBALL);
|
event.setSource(InputDevice.SOURCE_TRACKBALL);
|
||||||
}
|
}
|
||||||
InputManager.getInstance().injectInputEvent(event,
|
InputManager.getInstance().injectInputEvent(event,
|
||||||
|
|||||||
@@ -57,10 +57,11 @@ interface IInputManager {
|
|||||||
// Temporarily changes the pointer speed.
|
// Temporarily changes the pointer speed.
|
||||||
void tryPointerSpeed(int speed);
|
void tryPointerSpeed(int speed);
|
||||||
|
|
||||||
// Injects an input event into the system. To inject into windows owned by other
|
// Injects an input event into the system. The caller must have the INJECT_EVENTS permission.
|
||||||
// applications, the caller must have the INJECT_EVENTS permission.
|
// The caller can target windows owned by a certain UID by providing a valid UID, or by
|
||||||
|
// providing {@link android.os.Process#INVALID_UID} to target all windows.
|
||||||
@UnsupportedAppUsage
|
@UnsupportedAppUsage
|
||||||
boolean injectInputEvent(in InputEvent ev, int mode);
|
boolean injectInputEvent(in InputEvent ev, int mode, int targetUid);
|
||||||
|
|
||||||
VerifiedInputEvent verifyInputEvent(in InputEvent ev);
|
VerifiedInputEvent verifyInputEvent(in InputEvent ev);
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import android.os.IVibratorStateListener;
|
|||||||
import android.os.InputEventInjectionSync;
|
import android.os.InputEventInjectionSync;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
|
import android.os.Process;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.ServiceManager;
|
import android.os.ServiceManager;
|
||||||
import android.os.ServiceManager.ServiceNotFoundException;
|
import android.os.ServiceManager.ServiceNotFoundException;
|
||||||
@@ -1107,14 +1108,58 @@ public final class InputManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects an input event into the event system, targeting windows owned by the provided uid.
|
||||||
|
*
|
||||||
|
* If a valid targetUid is provided, the system will only consider injecting the input event
|
||||||
|
* into windows owned by the provided uid. If the input event is targeted at a window that is
|
||||||
|
* not owned by the provided uid, input injection will fail and a RemoteException will be
|
||||||
|
* thrown.
|
||||||
|
*
|
||||||
|
* The synchronization mode determines whether the method blocks while waiting for
|
||||||
|
* input injection to proceed.
|
||||||
|
* <p>
|
||||||
|
* Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission.
|
||||||
|
* </p><p>
|
||||||
|
* Make sure you correctly set the event time and input source of the event
|
||||||
|
* before calling this method.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param event The event to inject.
|
||||||
|
* @param mode The synchronization mode. One of:
|
||||||
|
* {@link android.os.InputEventInjectionSync.NONE},
|
||||||
|
* {@link android.os.InputEventInjectionSync.WAIT_FOR_RESULT}, or
|
||||||
|
* {@link android.os.InputEventInjectionSync.WAIT_FOR_FINISHED}.
|
||||||
|
* @param targetUid The uid to target, or {@link android.os.Process#INVALID_UID} to target all
|
||||||
|
* windows.
|
||||||
|
* @return True if input event injection succeeded.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@RequiresPermission(Manifest.permission.INJECT_EVENTS)
|
||||||
|
public boolean injectInputEvent(InputEvent event, int mode, int targetUid) {
|
||||||
|
if (event == null) {
|
||||||
|
throw new IllegalArgumentException("event must not be null");
|
||||||
|
}
|
||||||
|
if (mode != InputEventInjectionSync.NONE
|
||||||
|
&& mode != InputEventInjectionSync.WAIT_FOR_FINISHED
|
||||||
|
&& mode != InputEventInjectionSync.WAIT_FOR_RESULT) {
|
||||||
|
throw new IllegalArgumentException("mode is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return mIm.injectInputEvent(event, mode, targetUid);
|
||||||
|
} catch (RemoteException ex) {
|
||||||
|
throw ex.rethrowFromSystemServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injects an input event into the event system on behalf of an application.
|
* Injects an input event into the event system on behalf of an application.
|
||||||
* The synchronization mode determines whether the method blocks while waiting for
|
* The synchronization mode determines whether the method blocks while waiting for
|
||||||
* input injection to proceed.
|
* input injection to proceed.
|
||||||
* <p>
|
* <p>
|
||||||
* Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into
|
* Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission.
|
||||||
* windows that are owned by other applications.
|
|
||||||
* </p><p>
|
* </p><p>
|
||||||
* Make sure you correctly set the event time and input source of the event
|
* Make sure you correctly set the event time and input source of the event
|
||||||
* before calling this method.
|
* before calling this method.
|
||||||
@@ -1129,22 +1174,10 @@ public final class InputManager {
|
|||||||
*
|
*
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
|
@RequiresPermission(Manifest.permission.INJECT_EVENTS)
|
||||||
@UnsupportedAppUsage
|
@UnsupportedAppUsage
|
||||||
public boolean injectInputEvent(InputEvent event, int mode) {
|
public boolean injectInputEvent(InputEvent event, int mode) {
|
||||||
if (event == null) {
|
return injectInputEvent(event, mode, Process.INVALID_UID);
|
||||||
throw new IllegalArgumentException("event must not be null");
|
|
||||||
}
|
|
||||||
if (mode != InputEventInjectionSync.NONE
|
|
||||||
&& mode != InputEventInjectionSync.WAIT_FOR_FINISHED
|
|
||||||
&& mode != InputEventInjectionSync.WAIT_FOR_RESULT) {
|
|
||||||
throw new IllegalArgumentException("mode is invalid");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return mIm.injectInputEvent(event, mode);
|
|
||||||
} catch (RemoteException ex) {
|
|
||||||
throw ex.rethrowFromSystemServer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17215,17 +17215,17 @@ public class ActivityManagerService extends IActivityManager.Stub
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUidCurrentlyInstrumented(int uid) {
|
public int getInstrumentationSourceUid(int uid) {
|
||||||
synchronized (mProcLock) {
|
synchronized (mProcLock) {
|
||||||
for (int i = mActiveInstrumentation.size() - 1; i >= 0; i--) {
|
for (int i = mActiveInstrumentation.size() - 1; i >= 0; i--) {
|
||||||
ActiveInstrumentation activeInst = mActiveInstrumentation.get(i);
|
ActiveInstrumentation activeInst = mActiveInstrumentation.get(i);
|
||||||
if (!activeInst.mFinished && activeInst.mTargetInfo != null
|
if (!activeInst.mFinished && activeInst.mTargetInfo != null
|
||||||
&& activeInst.mTargetInfo.uid == uid) {
|
&& activeInst.mTargetInfo.uid == uid) {
|
||||||
return true;
|
return activeInst.mSourceUid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return INVALID_UID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2367,7 +2367,8 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
|
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
|
||||||
boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
|
boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
|
||||||
if (!isSelfRequest) {
|
if (!isSelfRequest) {
|
||||||
boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
|
boolean isCallerInstrumented =
|
||||||
|
ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
|
||||||
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
|
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
|
||||||
boolean isCallerPermissionController;
|
boolean isCallerPermissionController;
|
||||||
try {
|
try {
|
||||||
@@ -6894,7 +6895,8 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
@Override
|
@Override
|
||||||
public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
|
public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
|
||||||
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
|
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
|
||||||
boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
|
boolean isCallerInstrumented =
|
||||||
|
ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
|
||||||
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
|
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
|
||||||
if (!isCallerSystem && !isCallerInstrumented) {
|
if (!isCallerSystem && !isCallerInstrumented) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import static android.view.KeyEvent.KEYCODE_UNKNOWN;
|
|||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
|
import android.app.ActivityManagerInternal;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
@@ -325,8 +326,7 @@ public class InputManagerService extends IInputManager.Stub
|
|||||||
private static native void nativeSetMaximumObscuringOpacityForTouch(long ptr, float opacity);
|
private static native void nativeSetMaximumObscuringOpacityForTouch(long ptr, float opacity);
|
||||||
private static native void nativeSetBlockUntrustedTouchesMode(long ptr, int mode);
|
private static native void nativeSetBlockUntrustedTouchesMode(long ptr, int mode);
|
||||||
private static native int nativeInjectInputEvent(long ptr, InputEvent event,
|
private static native int nativeInjectInputEvent(long ptr, InputEvent event,
|
||||||
int injectorPid, int injectorUid, int syncMode, int timeoutMillis,
|
boolean injectIntoUid, int uid, int syncMode, int timeoutMillis, int policyFlags);
|
||||||
int policyFlags);
|
|
||||||
private static native VerifiedInputEvent nativeVerifyInputEvent(long ptr, InputEvent event);
|
private static native VerifiedInputEvent nativeVerifyInputEvent(long ptr, InputEvent event);
|
||||||
private static native void nativeToggleCapsLock(long ptr, int deviceId);
|
private static native void nativeToggleCapsLock(long ptr, int deviceId);
|
||||||
private static native void nativeDisplayRemoved(long ptr, int displayId);
|
private static native void nativeDisplayRemoved(long ptr, int displayId);
|
||||||
@@ -897,7 +897,15 @@ public class InputManagerService extends IInputManager.Stub
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override // Binder call
|
@Override // Binder call
|
||||||
public boolean injectInputEvent(InputEvent event, int mode) {
|
public boolean injectInputEvent(InputEvent event, int mode, int targetUid) {
|
||||||
|
if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
|
||||||
|
"injectInputEvent()", true /*checkInstrumentationSource*/)) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"Injecting input events requires the caller (or the source of the "
|
||||||
|
+ "instrumentation, if any) to have the INJECT_EVENTS permission.");
|
||||||
|
}
|
||||||
|
// We are not checking if targetUid matches the callingUid, since having the permission
|
||||||
|
// already means you can inject into any window.
|
||||||
Objects.requireNonNull(event, "event must not be null");
|
Objects.requireNonNull(event, "event must not be null");
|
||||||
if (mode != InputEventInjectionSync.NONE
|
if (mode != InputEventInjectionSync.NONE
|
||||||
&& mode != InputEventInjectionSync.WAIT_FOR_FINISHED
|
&& mode != InputEventInjectionSync.WAIT_FOR_FINISHED
|
||||||
@@ -906,22 +914,41 @@ public class InputManagerService extends IInputManager.Stub
|
|||||||
}
|
}
|
||||||
|
|
||||||
final int pid = Binder.getCallingPid();
|
final int pid = Binder.getCallingPid();
|
||||||
final int uid = Binder.getCallingUid();
|
|
||||||
final long ident = Binder.clearCallingIdentity();
|
final long ident = Binder.clearCallingIdentity();
|
||||||
|
final boolean injectIntoUid = targetUid != Process.INVALID_UID;
|
||||||
final int result;
|
final int result;
|
||||||
try {
|
try {
|
||||||
result = nativeInjectInputEvent(mPtr, event, pid, uid, mode,
|
result = nativeInjectInputEvent(mPtr, event, injectIntoUid,
|
||||||
INJECTION_TIMEOUT_MILLIS, WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
|
targetUid, mode, INJECTION_TIMEOUT_MILLIS,
|
||||||
|
WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
|
||||||
} finally {
|
} finally {
|
||||||
Binder.restoreCallingIdentity(ident);
|
Binder.restoreCallingIdentity(ident);
|
||||||
}
|
}
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case InputEventInjectionResult.PERMISSION_DENIED:
|
|
||||||
Slog.w(TAG, "Input event injection from pid " + pid + " permission denied.");
|
|
||||||
throw new SecurityException(
|
|
||||||
"Injecting to another application requires INJECT_EVENTS permission");
|
|
||||||
case InputEventInjectionResult.SUCCEEDED:
|
case InputEventInjectionResult.SUCCEEDED:
|
||||||
return true;
|
return true;
|
||||||
|
case InputEventInjectionResult.TARGET_MISMATCH:
|
||||||
|
if (!injectIntoUid) {
|
||||||
|
throw new IllegalStateException("Injection should not result in TARGET_MISMATCH"
|
||||||
|
+ " when it is not targeted into to a specific uid.");
|
||||||
|
}
|
||||||
|
// Attempt to inject into a window owned by the instrumentation source of the caller
|
||||||
|
// because it is possible that tests adopt the identity of the shell when launching
|
||||||
|
// activities that they would like to inject into.
|
||||||
|
final ActivityManagerInternal ami =
|
||||||
|
LocalServices.getService(ActivityManagerInternal.class);
|
||||||
|
Objects.requireNonNull(ami, "ActivityManagerInternal should not be null.");
|
||||||
|
final int instrUid = ami.getInstrumentationSourceUid(Binder.getCallingUid());
|
||||||
|
if (instrUid != Process.INVALID_UID && targetUid != instrUid) {
|
||||||
|
Slog.w(TAG, "Targeted input event was not directed at a window owned by uid "
|
||||||
|
+ targetUid + ". Attempting to inject into window owned by "
|
||||||
|
+ "instrumentation source uid " + instrUid + ".");
|
||||||
|
return injectInputEvent(event, mode, instrUid);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Targeted input event injection from pid " + pid
|
||||||
|
+ " was not directed at a window owned by uid "
|
||||||
|
+ targetUid + ".");
|
||||||
case InputEventInjectionResult.TIMED_OUT:
|
case InputEventInjectionResult.TIMED_OUT:
|
||||||
Slog.w(TAG, "Input event injection from pid " + pid + " timed out.");
|
Slog.w(TAG, "Input event injection from pid " + pid + " timed out.");
|
||||||
return false;
|
return false;
|
||||||
@@ -2757,8 +2784,12 @@ public class InputManagerService extends IInputManager.Stub
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkCallingPermission(String permission, String func) {
|
private boolean checkCallingPermission(String permission, String func) {
|
||||||
|
return checkCallingPermission(permission, func, false /*checkInstrumentationSource*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkCallingPermission(String permission, String func,
|
||||||
|
boolean checkInstrumentationSource) {
|
||||||
// Quick check: if the calling permission is me, it's all okay.
|
// Quick check: if the calling permission is me, it's all okay.
|
||||||
if (Binder.getCallingPid() == Process.myPid()) {
|
if (Binder.getCallingPid() == Process.myPid()) {
|
||||||
return true;
|
return true;
|
||||||
@@ -2767,6 +2798,18 @@ public class InputManagerService extends IInputManager.Stub
|
|||||||
if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
|
if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (checkInstrumentationSource) {
|
||||||
|
final ActivityManagerInternal ami =
|
||||||
|
LocalServices.getService(ActivityManagerInternal.class);
|
||||||
|
Objects.requireNonNull(ami, "ActivityManagerInternal should not be null.");
|
||||||
|
final int instrumentationUid = ami.getInstrumentationSourceUid(Binder.getCallingUid());
|
||||||
|
if (instrumentationUid != Process.INVALID_UID && mContext.checkPermission(permission,
|
||||||
|
-1 /*pid*/, instrumentationUid) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String msg = "Permission Denial: " + func + " from pid="
|
String msg = "Permission Denial: " + func + " from pid="
|
||||||
+ Binder.getCallingPid()
|
+ Binder.getCallingPid()
|
||||||
+ ", uid=" + Binder.getCallingUid()
|
+ ", uid=" + Binder.getCallingUid()
|
||||||
@@ -3010,13 +3053,6 @@ public class InputManagerService extends IInputManager.Stub
|
|||||||
return mWindowManagerCallbacks.dispatchUnhandledKey(focus, event, policyFlags);
|
return mWindowManagerCallbacks.dispatchUnhandledKey(focus, event, policyFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Native callback.
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private boolean checkInjectEventsPermission(int injectorPid, int injectorUid) {
|
|
||||||
return mContext.checkPermission(android.Manifest.permission.INJECT_EVENTS,
|
|
||||||
injectorPid, injectorUid) == PackageManager.PERMISSION_GRANTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Native callback.
|
// Native callback.
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private void onPointerDownOutsideFocus(IBinder touchedToken) {
|
private void onPointerDownOutsideFocus(IBinder touchedToken) {
|
||||||
@@ -3459,12 +3495,17 @@ public class InputManagerService extends IInputManager.Stub
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendInputEvent(InputEvent event, int policyFlags) {
|
public void sendInputEvent(InputEvent event, int policyFlags) {
|
||||||
|
if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
|
||||||
|
"sendInputEvent()")) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"The INJECT_EVENTS permission is required for injecting input events.");
|
||||||
|
}
|
||||||
Objects.requireNonNull(event, "event must not be null");
|
Objects.requireNonNull(event, "event must not be null");
|
||||||
|
|
||||||
synchronized (mInputFilterLock) {
|
synchronized (mInputFilterLock) {
|
||||||
if (!mDisconnected) {
|
if (!mDisconnected) {
|
||||||
nativeInjectInputEvent(mPtr, event, 0, 0,
|
nativeInjectInputEvent(mPtr, event, false /* injectIntoUid */, -1 /* uid */,
|
||||||
InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0,
|
InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0 /* timeout */,
|
||||||
policyFlags | WindowManagerPolicy.FLAG_FILTERED);
|
policyFlags | WindowManagerPolicy.FLAG_FILTERED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,7 +183,8 @@ public final class LogcatManagerService extends SystemService {
|
|||||||
|
|
||||||
ActivityManagerInternal ami = LocalServices.getService(
|
ActivityManagerInternal ami = LocalServices.getService(
|
||||||
ActivityManagerInternal.class);
|
ActivityManagerInternal.class);
|
||||||
boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(mUid);
|
boolean isCallerInstrumented =
|
||||||
|
ami.getInstrumentationSourceUid(mUid) != android.os.Process.INVALID_UID;
|
||||||
|
|
||||||
// The instrumented apks only run for testing, so we don't check user permission.
|
// The instrumented apks only run for testing, so we don't check user permission.
|
||||||
if (isCallerInstrumented) {
|
if (isCallerInstrumented) {
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ static struct {
|
|||||||
jmethodID interceptMotionBeforeQueueingNonInteractive;
|
jmethodID interceptMotionBeforeQueueingNonInteractive;
|
||||||
jmethodID interceptKeyBeforeDispatching;
|
jmethodID interceptKeyBeforeDispatching;
|
||||||
jmethodID dispatchUnhandledKey;
|
jmethodID dispatchUnhandledKey;
|
||||||
jmethodID checkInjectEventsPermission;
|
|
||||||
jmethodID onPointerDownOutsideFocus;
|
jmethodID onPointerDownOutsideFocus;
|
||||||
jmethodID getVirtualKeyQuietTimeMillis;
|
jmethodID getVirtualKeyQuietTimeMillis;
|
||||||
jmethodID getExcludedDeviceNames;
|
jmethodID getExcludedDeviceNames;
|
||||||
@@ -329,7 +328,6 @@ public:
|
|||||||
bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent,
|
bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent,
|
||||||
uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) override;
|
uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) override;
|
||||||
void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
|
void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
|
||||||
bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override;
|
|
||||||
void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
|
void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
|
||||||
void setPointerCapture(const PointerCaptureRequest& request) override;
|
void setPointerCapture(const PointerCaptureRequest& request) override;
|
||||||
void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
|
void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
|
||||||
@@ -1368,18 +1366,6 @@ void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType,
|
|||||||
android_server_PowerManagerService_userActivity(eventTime, eventType, displayId);
|
android_server_PowerManagerService_userActivity(eventTime, eventType, displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NativeInputManager::checkInjectEventsPermissionNonReentrant(
|
|
||||||
int32_t injectorPid, int32_t injectorUid) {
|
|
||||||
ATRACE_CALL();
|
|
||||||
JNIEnv* env = jniEnv();
|
|
||||||
jboolean result = env->CallBooleanMethod(mServiceObj,
|
|
||||||
gServiceClassInfo.checkInjectEventsPermission, injectorPid, injectorUid);
|
|
||||||
if (checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission")) {
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) {
|
void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) {
|
||||||
ATRACE_CALL();
|
ATRACE_CALL();
|
||||||
JNIEnv* env = jniEnv();
|
JNIEnv* env = jniEnv();
|
||||||
@@ -1706,11 +1692,12 @@ static void nativeSetBlockUntrustedTouchesMode(JNIEnv* env, jclass /* clazz */,
|
|||||||
static_cast<BlockUntrustedTouchesMode>(mode));
|
static_cast<BlockUntrustedTouchesMode>(mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
|
static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */, jlong ptr,
|
||||||
jlong ptr, jobject inputEventObj, jint injectorPid, jint injectorUid,
|
jobject inputEventObj, jboolean injectIntoUid, jint uid,
|
||||||
jint syncMode, jint timeoutMillis, jint policyFlags) {
|
jint syncMode, jint timeoutMillis, jint policyFlags) {
|
||||||
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
|
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
|
||||||
|
|
||||||
|
const std::optional<int32_t> targetUid = injectIntoUid ? std::make_optional(uid) : std::nullopt;
|
||||||
// static_cast is safe because the value was already checked at the Java layer
|
// static_cast is safe because the value was already checked at the Java layer
|
||||||
InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode);
|
InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode);
|
||||||
|
|
||||||
@@ -1723,8 +1710,7 @@ static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const InputEventInjectionResult result =
|
const InputEventInjectionResult result =
|
||||||
im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, injectorPid,
|
im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, targetUid, mode,
|
||||||
injectorUid, mode,
|
|
||||||
std::chrono::milliseconds(
|
std::chrono::milliseconds(
|
||||||
timeoutMillis),
|
timeoutMillis),
|
||||||
uint32_t(policyFlags));
|
uint32_t(policyFlags));
|
||||||
@@ -1737,8 +1723,8 @@ static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const InputEventInjectionResult result =
|
const InputEventInjectionResult result =
|
||||||
im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, injectorPid,
|
im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, targetUid,
|
||||||
injectorUid, mode,
|
mode,
|
||||||
std::chrono::milliseconds(
|
std::chrono::milliseconds(
|
||||||
timeoutMillis),
|
timeoutMillis),
|
||||||
uint32_t(policyFlags));
|
uint32_t(policyFlags));
|
||||||
@@ -2359,7 +2345,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
|
|||||||
{"nativeSetMaximumObscuringOpacityForTouch", "(JF)V",
|
{"nativeSetMaximumObscuringOpacityForTouch", "(JF)V",
|
||||||
(void*)nativeSetMaximumObscuringOpacityForTouch},
|
(void*)nativeSetMaximumObscuringOpacityForTouch},
|
||||||
{"nativeSetBlockUntrustedTouchesMode", "(JI)V", (void*)nativeSetBlockUntrustedTouchesMode},
|
{"nativeSetBlockUntrustedTouchesMode", "(JI)V", (void*)nativeSetBlockUntrustedTouchesMode},
|
||||||
{"nativeInjectInputEvent", "(JLandroid/view/InputEvent;IIIII)I",
|
{"nativeInjectInputEvent", "(JLandroid/view/InputEvent;ZIIII)I",
|
||||||
(void*)nativeInjectInputEvent},
|
(void*)nativeInjectInputEvent},
|
||||||
{"nativeVerifyInputEvent", "(JLandroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;",
|
{"nativeVerifyInputEvent", "(JLandroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;",
|
||||||
(void*)nativeVerifyInputEvent},
|
(void*)nativeVerifyInputEvent},
|
||||||
@@ -2499,9 +2485,6 @@ int register_android_server_InputManager(JNIEnv* env) {
|
|||||||
"dispatchUnhandledKey",
|
"dispatchUnhandledKey",
|
||||||
"(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
|
"(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
|
||||||
|
|
||||||
GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
|
|
||||||
"checkInjectEventsPermission", "(II)Z");
|
|
||||||
|
|
||||||
GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz,
|
GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz,
|
||||||
"onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V");
|
"onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user