diff --git a/api/current.txt b/api/current.txt index 1d30f513bb768..1e9dc2973aa45 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4195,6 +4195,7 @@ package android.app { method public final boolean performGlobalAction(int); method public void setOnAccessibilityEventListener(android.app.UiAutomation.OnAccessibilityEventListener); method public boolean setRotation(int); + method public void setRunAsMonkey(boolean); method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); method public android.graphics.Bitmap takeScreenshot(); method public void waitForIdle(long, long) throws java.util.concurrent.TimeoutException; diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index c99051ba4dd67..98baa0ebb6319 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1413,6 +1413,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case SET_USER_IS_MONKEY_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + final boolean monkey = (data.readInt() == 1); + setUserIsMonkey(monkey); + reply.writeNoException(); + return true; + } + case FINISH_HEAVY_WEIGHT_APP_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); finishHeavyWeightApp(); @@ -3633,7 +3641,18 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return res; } - + + public void setUserIsMonkey(boolean monkey) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(monkey ? 1 : 0); + mRemote.transact(SET_USER_IS_MONKEY_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void finishHeavyWeightApp() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index fa8839a66b3d2..33a27709b039a 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -285,7 +285,9 @@ public interface IActivityManager extends IInterface { int enterAnim, int exitAnim) throws RemoteException; public boolean isUserAMonkey() throws RemoteException; - + + public void setUserIsMonkey(boolean monkey) throws RemoteException; + public void finishHeavyWeightApp() throws RemoteException; public void setImmersive(IBinder token, boolean immersive) throws RemoteException; @@ -635,4 +637,5 @@ public interface IActivityManager extends IInterface { int REPORT_TOP_ACTIVITY_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+162; int GET_LAUNCHED_FROM_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+163; int KILL_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+164; + int SET_USER_IS_MONKEY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+165; } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index e0dfb254cd1a7..a307a73d52b3a 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -189,6 +189,10 @@ public class Instrumentation { if (mPerfMetrics != null) { results.putAll(mPerfMetrics); } + if (mUiAutomation != null) { + mUiAutomation.disconnect(); + mUiAutomation = null; + } mThread.finishInstrumentation(resultCode, results); } @@ -1695,10 +1699,6 @@ public class Instrumentation { startPerformanceSnapshot(); } onStart(); - if (mUiAutomation != null) { - mUiAutomation.disconnect(); - mUiAutomation = null; - } } } diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index d9799b6529191..05b79c1d7a8ed 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -619,6 +619,25 @@ public final class UiAutomation { return screenShot; } + /** + * Sets whether this UiAutomation to run in a "monkey" mode. Applications can query whether + * they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing + * potentially undesirable actions such as calling 911 or posting on public forums etc. + * + * @param enable whether to run in a "monkey" mode or not. Default is not. + * @see {@link ActivityManager#isUserAMonkey()} + */ + public void setRunAsMonkey(boolean enable) { + synchronized (mLock) { + throwIfNotConnectedLocked(); + } + try { + ActivityManagerNative.getDefault().setUserIsMonkey(enable); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while setting run as monkey!", re); + } + } + private static float getDegreesForRotation(int value) { switch (value) { case Surface.ROTATION_90: { diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 2417cff93d013..bc1df8591831b 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -936,6 +936,12 @@ public final class ActivityManagerService extends ActivityManagerNative CompatModeDialog mCompatModeDialog; long mLastMemUsageReportTime = 0; + /** + * Flag whether the current user is a "monkey", i.e. whether + * the UI is driven by a UI automation tool. + */ + private boolean mUserIsMonkey; + final Handler mHandler = new Handler() { //public Handler() { // if (localLOGV) Slog.v(TAG, "Handler started!"); @@ -7434,11 +7440,27 @@ public final class ActivityManagerService extends ActivityManagerNative } } - public boolean isUserAMonkey() { - // For now the fact that there is a controller implies - // we have a monkey. + public void setUserIsMonkey(boolean userIsMonkey) { synchronized (this) { - return mController != null; + synchronized (mPidsSelfLocked) { + final int callingPid = Binder.getCallingPid(); + ProcessRecord precessRecord = mPidsSelfLocked.get(callingPid); + if (precessRecord == null) { + throw new SecurityException("Unknown process: " + callingPid); + } + if (precessRecord.instrumentationUiAutomationConnection == null) { + throw new SecurityException("Only an instrumentation process " + + "with a UiAutomation can call setUserIsMonkey"); + } + } + mUserIsMonkey = userIsMonkey; + } + } + + public boolean isUserAMonkey() { + synchronized (this) { + // If there is a controller also implies the user is a monkey. + return (mUserIsMonkey || mController != null); } } @@ -12435,6 +12457,9 @@ public final class ActivityManagerService extends ActivityManagerNative } catch (RemoteException re) { /* ignore */ } + // Only a UiAutomation can set this flag and now that + // it is finished we make sure it is reset to its default. + mUserIsMonkey = false; } app.instrumentationWatcher = null; app.instrumentationUiAutomationConnection = null;