2688 lines
107 KiB
Java
2688 lines
107 KiB
Java
/*
|
|
* Copyright (C) 2006 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 android.app;
|
|
|
|
import android.annotation.FlaggedApi;
|
|
import android.annotation.IntDef;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.compat.annotation.UnsupportedAppUsage;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.pm.ActivityInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.res.Configuration;
|
|
import android.hardware.input.InputManager;
|
|
import android.hardware.input.InputManagerGlobal;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.os.Debug;
|
|
import android.os.IBinder;
|
|
import android.os.Looper;
|
|
import android.os.MessageQueue;
|
|
import android.os.PerformanceCollector;
|
|
import android.os.PersistableBundle;
|
|
import android.os.Process;
|
|
import android.os.RemoteException;
|
|
import android.os.ServiceManager;
|
|
import android.os.SystemClock;
|
|
import android.os.SystemProperties;
|
|
import android.os.TestLooperManager;
|
|
import android.os.UserHandle;
|
|
import android.os.UserManager;
|
|
import android.util.AndroidRuntimeException;
|
|
import android.util.Log;
|
|
import android.view.Display;
|
|
import android.view.IWindowManager;
|
|
import android.view.InputDevice;
|
|
import android.view.KeyCharacterMap;
|
|
import android.view.KeyEvent;
|
|
import android.view.MotionEvent;
|
|
import android.view.SurfaceControl;
|
|
import android.view.ViewConfiguration;
|
|
import android.view.Window;
|
|
import android.view.WindowManagerGlobal;
|
|
|
|
import com.android.internal.content.ReferrerIntent;
|
|
|
|
import java.io.File;
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.StringJoiner;
|
|
import java.util.concurrent.TimeoutException;
|
|
|
|
/**
|
|
* Base class for implementing application instrumentation code. When running
|
|
* with instrumentation turned on, this class will be instantiated for you
|
|
* before any of the application code, allowing you to monitor all of the
|
|
* interaction the system has with the application. An Instrumentation
|
|
* implementation is described to the system through an AndroidManifest.xml's
|
|
* <instrumentation> tag.
|
|
*/
|
|
@android.ravenwood.annotation.RavenwoodKeepPartialClass
|
|
public class Instrumentation {
|
|
|
|
/**
|
|
* If included in the status or final bundle sent to an IInstrumentationWatcher, this key
|
|
* identifies the class that is writing the report. This can be used to provide more structured
|
|
* logging or reporting capabilities in the IInstrumentationWatcher.
|
|
*/
|
|
public static final String REPORT_KEY_IDENTIFIER = "id";
|
|
/**
|
|
* If included in the status or final bundle sent to an IInstrumentationWatcher, this key
|
|
* identifies a string which can simply be printed to the output stream. Using these streams
|
|
* provides a "pretty printer" version of the status & final packets. Any bundles including
|
|
* this key should also include the complete set of raw key/value pairs, so that the
|
|
* instrumentation can also be launched, and results collected, by an automated system.
|
|
*/
|
|
public static final String REPORT_KEY_STREAMRESULT = "stream";
|
|
|
|
private static final String TAG = "Instrumentation";
|
|
|
|
private static final long CONNECT_TIMEOUT_MILLIS = 60_000;
|
|
|
|
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
|
|
|
|
// If set, will print the stack trace for activity starts within the process
|
|
static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE &&
|
|
SystemProperties.getBoolean("persist.wm.debug.start_activity", false);
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
@IntDef({0, UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES,
|
|
UiAutomation.FLAG_DONT_USE_ACCESSIBILITY})
|
|
public @interface UiAutomationFlags {};
|
|
|
|
|
|
private final Object mSync = new Object();
|
|
private ActivityThread mThread = null;
|
|
private MessageQueue mMessageQueue = null;
|
|
private Context mInstrContext;
|
|
private Context mAppContext;
|
|
private ComponentName mComponent;
|
|
private Thread mRunner;
|
|
private List<ActivityWaiter> mWaitingActivities;
|
|
private List<ActivityMonitor> mActivityMonitors;
|
|
private IInstrumentationWatcher mWatcher;
|
|
private IUiAutomationConnection mUiAutomationConnection;
|
|
private boolean mAutomaticPerformanceSnapshots = false;
|
|
private PerformanceCollector mPerformanceCollector;
|
|
private Bundle mPerfMetrics = new Bundle();
|
|
private UiAutomation mUiAutomation;
|
|
private final Object mAnimationCompleteLock = new Object();
|
|
|
|
@android.ravenwood.annotation.RavenwoodKeep
|
|
public Instrumentation() {
|
|
}
|
|
|
|
/**
|
|
* Called for methods that shouldn't be called by standard apps and
|
|
* should only be used in instrumentation environments. This is not
|
|
* security feature as these classes will still be accessible through
|
|
* reflection, but it will serve as noticeable discouragement from
|
|
* doing such a thing.
|
|
*/
|
|
@android.ravenwood.annotation.RavenwoodKeep
|
|
private void checkInstrumenting(String method) {
|
|
// Check if we have an instrumentation context, as init should only get called by
|
|
// the system in startup processes that are being instrumented.
|
|
if (mInstrContext == null) {
|
|
throw new RuntimeException(method +
|
|
" cannot be called outside of instrumented processes");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns if it is being called in an instrumentation environment.
|
|
*
|
|
* @hide
|
|
*/
|
|
@android.ravenwood.annotation.RavenwoodKeep
|
|
public boolean isInstrumenting() {
|
|
// Check if we have an instrumentation context, as init should only get called by
|
|
// the system in startup processes that are being instrumented.
|
|
if (mInstrContext == null) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called when the instrumentation is starting, before any application code
|
|
* has been loaded. Usually this will be implemented to simply call
|
|
* {@link #start} to begin the instrumentation thread, which will then
|
|
* continue execution in {@link #onStart}.
|
|
*
|
|
* <p>If you do not need your own thread -- that is you are writing your
|
|
* instrumentation to be completely asynchronous (returning to the event
|
|
* loop so that the application can run), you can simply begin your
|
|
* instrumentation here, for example call {@link Context#startActivity} to
|
|
* begin the appropriate first activity of the application.
|
|
*
|
|
* @param arguments Any additional arguments that were supplied when the
|
|
* instrumentation was started.
|
|
*/
|
|
public void onCreate(Bundle arguments) {
|
|
}
|
|
|
|
/**
|
|
* Create and start a new thread in which to run instrumentation. This new
|
|
* thread will call to {@link #onStart} where you can implement the
|
|
* instrumentation.
|
|
*/
|
|
public void start() {
|
|
if (mRunner != null) {
|
|
throw new RuntimeException("Instrumentation already started");
|
|
}
|
|
mRunner = new InstrumentationThread("Instr: " + getClass().getName());
|
|
mRunner.start();
|
|
}
|
|
|
|
/**
|
|
* Method where the instrumentation thread enters execution. This allows
|
|
* you to run your instrumentation code in a separate thread than the
|
|
* application, so that it can perform blocking operation such as
|
|
* {@link #sendKeySync} or {@link #startActivitySync}.
|
|
*
|
|
* <p>You will typically want to call finish() when this function is done,
|
|
* to end your instrumentation.
|
|
*/
|
|
public void onStart() {
|
|
}
|
|
|
|
/**
|
|
* This is called whenever the system captures an unhandled exception that
|
|
* was thrown by the application. The default implementation simply
|
|
* returns false, allowing normal system handling of the exception to take
|
|
* place.
|
|
*
|
|
* @param obj The client object that generated the exception. May be an
|
|
* Application, Activity, BroadcastReceiver, Service, or null.
|
|
* @param e The exception that was thrown.
|
|
*
|
|
* @return To allow normal system exception process to occur, return false.
|
|
* If true is returned, the system will proceed as if the exception
|
|
* didn't happen.
|
|
*/
|
|
public boolean onException(Object obj, Throwable e) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Provide a status report about the application.
|
|
*
|
|
* @param resultCode Current success/failure of instrumentation.
|
|
* @param results Any results to send back to the code that started the instrumentation.
|
|
*/
|
|
public void sendStatus(int resultCode, Bundle results) {
|
|
if (mWatcher != null) {
|
|
try {
|
|
mWatcher.instrumentationStatus(mComponent, resultCode, results);
|
|
}
|
|
catch (RemoteException e) {
|
|
mWatcher = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Report some results in the middle of instrumentation execution. Later results (including
|
|
* those provided by {@link #finish}) will be combined with {@link Bundle#putAll}.
|
|
*/
|
|
public void addResults(Bundle results) {
|
|
IActivityManager am = ActivityManager.getService();
|
|
try {
|
|
am.addInstrumentationResults(mThread.getApplicationThread(), results);
|
|
} catch (RemoteException ex) {
|
|
throw ex.rethrowFromSystemServer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Terminate instrumentation of the application. This will cause the
|
|
* application process to exit, removing this instrumentation from the next
|
|
* time the application is started. If multiple processes are currently running
|
|
* for this instrumentation, all of those processes will be killed.
|
|
*
|
|
* @param resultCode Overall success/failure of instrumentation.
|
|
* @param results Any results to send back to the code that started the
|
|
* instrumentation.
|
|
*/
|
|
public void finish(int resultCode, Bundle results) {
|
|
if (mAutomaticPerformanceSnapshots) {
|
|
endPerformanceSnapshot();
|
|
}
|
|
if (mPerfMetrics != null) {
|
|
if (results == null) {
|
|
results = new Bundle();
|
|
}
|
|
results.putAll(mPerfMetrics);
|
|
}
|
|
if ((mUiAutomation != null) && !mUiAutomation.isDestroyed()) {
|
|
mUiAutomation.disconnect();
|
|
mUiAutomation = null;
|
|
}
|
|
mThread.finishInstrumentation(resultCode, results);
|
|
}
|
|
|
|
public void setAutomaticPerformanceSnapshots() {
|
|
mAutomaticPerformanceSnapshots = true;
|
|
mPerformanceCollector = new PerformanceCollector();
|
|
}
|
|
|
|
public void startPerformanceSnapshot() {
|
|
if (!isProfiling()) {
|
|
mPerformanceCollector.beginSnapshot(null);
|
|
}
|
|
}
|
|
|
|
public void endPerformanceSnapshot() {
|
|
if (!isProfiling()) {
|
|
mPerfMetrics = mPerformanceCollector.endSnapshot();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when the instrumented application is stopping, after all of the
|
|
* normal application cleanup has occurred.
|
|
*/
|
|
public void onDestroy() {
|
|
}
|
|
|
|
/**
|
|
* Return the Context of this instrumentation's package. Note that this is
|
|
* often different than the Context of the application being
|
|
* instrumentated, since the instrumentation code often lives is a
|
|
* different package than that of the application it is running against.
|
|
* See {@link #getTargetContext} to retrieve a Context for the target
|
|
* application.
|
|
*
|
|
* @return The instrumentation's package context.
|
|
*
|
|
* @see #getTargetContext
|
|
*/
|
|
@android.ravenwood.annotation.RavenwoodKeep
|
|
public Context getContext() {
|
|
return mInstrContext;
|
|
}
|
|
|
|
/**
|
|
* Returns complete component name of this instrumentation.
|
|
*
|
|
* @return Returns the complete component name for this instrumentation.
|
|
*/
|
|
public ComponentName getComponentName() {
|
|
return mComponent;
|
|
}
|
|
|
|
/**
|
|
* Return a Context for the target application being instrumented. Note
|
|
* that this is often different than the Context of the instrumentation
|
|
* code, since the instrumentation code often lives is a different package
|
|
* than that of the application it is running against. See
|
|
* {@link #getContext} to retrieve a Context for the instrumentation code.
|
|
*
|
|
* @return A Context in the target application.
|
|
*
|
|
* @see #getContext
|
|
*/
|
|
@android.ravenwood.annotation.RavenwoodKeep
|
|
public Context getTargetContext() {
|
|
return mAppContext;
|
|
}
|
|
|
|
/**
|
|
* Return the name of the process this instrumentation is running in. Note this should
|
|
* only be used for testing and debugging. If you are thinking about using this to,
|
|
* for example, conditionalize what is initialized in an Application class, it is strongly
|
|
* recommended to instead use lazy initialization (such as a getter for the state that
|
|
* only creates it when requested). This can greatly reduce the work your process does
|
|
* when created for secondary things, such as to receive a broadcast.
|
|
*/
|
|
public String getProcessName() {
|
|
return mThread.getProcessName();
|
|
}
|
|
|
|
/**
|
|
* Check whether this instrumentation was started with profiling enabled.
|
|
*
|
|
* @return Returns true if profiling was enabled when starting, else false.
|
|
*/
|
|
public boolean isProfiling() {
|
|
return mThread.isProfiling();
|
|
}
|
|
|
|
/**
|
|
* This method will start profiling if isProfiling() returns true. You should
|
|
* only call this method if you set the handleProfiling attribute in the
|
|
* manifest file for this Instrumentation to true.
|
|
*/
|
|
public void startProfiling() {
|
|
if (mThread.isProfiling()) {
|
|
File file = new File(mThread.getProfileFilePath());
|
|
file.getParentFile().mkdirs();
|
|
Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stops profiling if isProfiling() returns true.
|
|
*/
|
|
public void stopProfiling() {
|
|
if (mThread.isProfiling()) {
|
|
Debug.stopMethodTracing();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Force the global system in or out of touch mode. This can be used if your
|
|
* instrumentation relies on the UI being in one more or the other when it starts.
|
|
*
|
|
* <p><b>Note:</b> Starting from Android {@link Build.VERSION_CODES#TIRAMISU}, this method
|
|
* will only take effect if the instrumentation was sourced from a process with
|
|
* {@code MODIFY_TOUCH_MODE_STATE} internal permission granted (shell already have it).
|
|
*
|
|
* @param inTouch Set to true to be in touch mode, false to be in focus mode.
|
|
*/
|
|
public void setInTouchMode(boolean inTouch) {
|
|
try {
|
|
IWindowManager.Stub.asInterface(
|
|
ServiceManager.getService("window")).setInTouchModeOnAllDisplays(inTouch);
|
|
} catch (RemoteException e) {
|
|
// Shouldn't happen!
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets the {@link #setInTouchMode touch mode} to the device default.
|
|
*/
|
|
public void resetInTouchMode() {
|
|
final boolean defaultInTouchMode = getContext().getResources().getBoolean(
|
|
com.android.internal.R.bool.config_defaultInTouchMode);
|
|
setInTouchMode(defaultInTouchMode);
|
|
}
|
|
|
|
/**
|
|
* Schedule a callback for when the application's main thread goes idle
|
|
* (has no more events to process).
|
|
*
|
|
* @param recipient Called the next time the thread's message queue is
|
|
* idle.
|
|
*/
|
|
public void waitForIdle(Runnable recipient) {
|
|
mMessageQueue.addIdleHandler(new Idler(recipient));
|
|
mThread.getHandler().post(new EmptyRunnable());
|
|
}
|
|
|
|
/**
|
|
* Synchronously wait for the application to be idle. Can not be called
|
|
* from the main application thread -- use {@link #start} to execute
|
|
* instrumentation in its own thread.
|
|
*/
|
|
public void waitForIdleSync() {
|
|
validateNotAppThread();
|
|
Idler idler = new Idler(null);
|
|
mMessageQueue.addIdleHandler(idler);
|
|
mThread.getHandler().post(new EmptyRunnable());
|
|
idler.waitForIdle();
|
|
}
|
|
|
|
private void waitForEnterAnimationComplete(Activity activity) {
|
|
synchronized (mAnimationCompleteLock) {
|
|
long timeout = 5000;
|
|
try {
|
|
// We need to check that this specified Activity completed the animation, not just
|
|
// any Activity. If it was another Activity, then decrease the timeout by how long
|
|
// it's already waited and wait for the thread to wakeup again.
|
|
while (timeout > 0 && !activity.mEnterAnimationComplete) {
|
|
long startTime = System.currentTimeMillis();
|
|
mAnimationCompleteLock.wait(timeout);
|
|
long totalTime = System.currentTimeMillis() - startTime;
|
|
timeout -= totalTime;
|
|
}
|
|
} catch (InterruptedException e) {
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @hide */
|
|
public void onEnterAnimationComplete() {
|
|
synchronized (mAnimationCompleteLock) {
|
|
mAnimationCompleteLock.notifyAll();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute a call on the application's main thread, blocking until it is
|
|
* complete. Useful for doing things that are not thread-safe, such as
|
|
* looking at or modifying the view hierarchy.
|
|
*
|
|
* @param runner The code to run on the main thread.
|
|
*/
|
|
public void runOnMainSync(Runnable runner) {
|
|
validateNotAppThread();
|
|
SyncRunnable sr = new SyncRunnable(runner);
|
|
mThread.getHandler().post(sr);
|
|
sr.waitForComplete();
|
|
}
|
|
|
|
boolean isSdkSandboxAllowedToStartActivities() {
|
|
return Process.isSdkSandbox()
|
|
&& mThread != null
|
|
&& mThread.mBoundApplication != null
|
|
&& mThread.mBoundApplication.isSdkInSandbox
|
|
&& getContext() != null
|
|
&& (getContext()
|
|
.checkSelfPermission(
|
|
android.Manifest.permission
|
|
.START_ACTIVITIES_FROM_SDK_SANDBOX)
|
|
== PackageManager.PERMISSION_GRANTED);
|
|
}
|
|
|
|
/**
|
|
* Activity name resolution for CTS-in-SdkSandbox tests requires some adjustments. Intents
|
|
* generated using {@link Context#getPackageName()} use the SDK sandbox package name in the
|
|
* component field instead of the test package name. An SDK-in-sandbox test attempting to launch
|
|
* an activity in the test package will encounter name resolution errors when resolving the
|
|
* activity name in the SDK sandbox package.
|
|
*
|
|
* <p>This function replaces the package name of the input intent component to allow activities
|
|
* belonging to a CTS-in-sandbox test to resolve correctly.
|
|
*
|
|
* @param intent the intent to modify to allow CTS-in-sandbox activity resolution.
|
|
*/
|
|
private void adjustIntentForCtsInSdkSandboxInstrumentation(@NonNull Intent intent) {
|
|
if (mComponent != null
|
|
&& intent.getComponent() != null
|
|
&& getContext()
|
|
.getPackageManager()
|
|
.getSdkSandboxPackageName()
|
|
.equals(intent.getComponent().getPackageName())) {
|
|
// Resolve the intent target for the test package, not for the sandbox package.
|
|
intent.setComponent(
|
|
new ComponentName(
|
|
mComponent.getPackageName(), intent.getComponent().getClassName()));
|
|
}
|
|
// We match the intent identifier against the running instrumentations for the sandbox.
|
|
intent.setIdentifier(mComponent.getPackageName());
|
|
}
|
|
|
|
private ActivityInfo resolveActivityInfoForCtsInSandbox(@NonNull Intent intent) {
|
|
adjustIntentForCtsInSdkSandboxInstrumentation(intent);
|
|
ActivityInfo ai = intent.resolveActivityInfo(getTargetContext().getPackageManager(), 0);
|
|
if (ai != null) {
|
|
ai.processName = mThread.getProcessName();
|
|
}
|
|
return ai;
|
|
}
|
|
|
|
/**
|
|
* Start a new activity and wait for it to begin running before returning.
|
|
* In addition to being synchronous, this method as some semantic
|
|
* differences from the standard {@link Context#startActivity} call: the
|
|
* activity component is resolved before talking with the activity manager
|
|
* (its class name is specified in the Intent that this method ultimately
|
|
* starts), and it does not allow you to start activities that run in a
|
|
* different process. In addition, if the given Intent resolves to
|
|
* multiple activities, instead of displaying a dialog for the user to
|
|
* select an activity, an exception will be thrown.
|
|
*
|
|
* <p>The function returns as soon as the activity goes idle following the
|
|
* call to its {@link Activity#onCreate}. Generally this means it has gone
|
|
* through the full initialization including {@link Activity#onResume} and
|
|
* drawn and displayed its initial window.
|
|
*
|
|
* @param intent Description of the activity to start.
|
|
*
|
|
* @see Context#startActivity
|
|
* @see #startActivitySync(Intent, Bundle)
|
|
*/
|
|
public Activity startActivitySync(Intent intent) {
|
|
return startActivitySync(intent, null /* options */);
|
|
}
|
|
|
|
/**
|
|
* Start a new activity and wait for it to begin running before returning.
|
|
* In addition to being synchronous, this method as some semantic
|
|
* differences from the standard {@link Context#startActivity} call: the
|
|
* activity component is resolved before talking with the activity manager
|
|
* (its class name is specified in the Intent that this method ultimately
|
|
* starts), and it does not allow you to start activities that run in a
|
|
* different process. In addition, if the given Intent resolves to
|
|
* multiple activities, instead of displaying a dialog for the user to
|
|
* select an activity, an exception will be thrown.
|
|
*
|
|
* <p>The function returns as soon as the activity goes idle following the
|
|
* call to its {@link Activity#onCreate}. Generally this means it has gone
|
|
* through the full initialization including {@link Activity#onResume} and
|
|
* drawn and displayed its initial window.
|
|
*
|
|
* @param intent Description of the activity to start.
|
|
* @param options Additional options for how the Activity should be started.
|
|
* May be null if there are no options. See {@link android.app.ActivityOptions}
|
|
* for how to build the Bundle supplied here; there are no supported definitions
|
|
* for building it manually.
|
|
*
|
|
* @see Context#startActivity(Intent, Bundle)
|
|
*/
|
|
@NonNull
|
|
public Activity startActivitySync(@NonNull Intent intent, @Nullable Bundle options) {
|
|
if (DEBUG_START_ACTIVITY) {
|
|
Log.d(TAG, "startActivity: intent=" + intent + " options=" + options, new Throwable());
|
|
}
|
|
validateNotAppThread();
|
|
|
|
final Activity activity;
|
|
synchronized (mSync) {
|
|
intent = new Intent(intent);
|
|
|
|
ActivityInfo ai =
|
|
isSdkSandboxAllowedToStartActivities()
|
|
? resolveActivityInfoForCtsInSandbox(intent)
|
|
: intent.resolveActivityInfo(getTargetContext().getPackageManager(), 0);
|
|
if (ai == null) {
|
|
throw new RuntimeException("Unable to resolve activity for: " + intent);
|
|
}
|
|
String myProc = mThread.getProcessName();
|
|
if (!ai.processName.equals(myProc)) {
|
|
// todo: if this intent is ambiguous, look here to see if
|
|
// there is a single match that is in our package.
|
|
throw new RuntimeException("Intent in process "
|
|
+ myProc + " resolved to different process "
|
|
+ ai.processName + ": " + intent);
|
|
}
|
|
|
|
intent.setComponent(new ComponentName(
|
|
ai.applicationInfo.packageName, ai.name));
|
|
final ActivityWaiter aw = new ActivityWaiter(intent);
|
|
|
|
if (mWaitingActivities == null) {
|
|
mWaitingActivities = new ArrayList();
|
|
}
|
|
mWaitingActivities.add(aw);
|
|
|
|
getTargetContext().startActivity(intent, options);
|
|
|
|
do {
|
|
try {
|
|
mSync.wait();
|
|
} catch (InterruptedException e) {
|
|
}
|
|
} while (mWaitingActivities.contains(aw));
|
|
activity = aw.activity;
|
|
}
|
|
|
|
// Do not call this method within mSync, lest it could block the main thread.
|
|
waitForEnterAnimationComplete(activity);
|
|
|
|
// Apply an empty transaction to ensure SF has a chance to update before
|
|
// the Activity is ready (b/138263890).
|
|
try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
|
|
t.apply(true);
|
|
}
|
|
return activity;
|
|
}
|
|
|
|
/**
|
|
* Information about a particular kind of Intent that is being monitored.
|
|
* An instance of this class is added to the
|
|
* current instrumentation through {@link #addMonitor}; after being added,
|
|
* when a new activity is being started the monitor will be checked and, if
|
|
* matching, its hit count updated and (optionally) the call stopped and a
|
|
* canned result returned.
|
|
*
|
|
* <p>An ActivityMonitor can also be used to look for the creation of an
|
|
* activity, through the {@link #waitForActivity} method. This will return
|
|
* after a matching activity has been created with that activity object.
|
|
*/
|
|
public static class ActivityMonitor {
|
|
private final IntentFilter mWhich;
|
|
private final String mClass;
|
|
private final ActivityResult mResult;
|
|
private final boolean mBlock;
|
|
private final boolean mIgnoreMatchingSpecificIntents;
|
|
|
|
|
|
// This is protected by 'Instrumentation.this.mSync'.
|
|
/*package*/ int mHits = 0;
|
|
|
|
// This is protected by 'this'.
|
|
/*package*/ Activity mLastActivity = null;
|
|
|
|
/**
|
|
* Create a new ActivityMonitor that looks for a particular kind of
|
|
* intent to be started.
|
|
*
|
|
* @param which The set of intents this monitor is responsible for.
|
|
* @param result A canned result to return if the monitor is hit; can
|
|
* be null.
|
|
* @param block Controls whether the monitor should block the activity
|
|
* start (returning its canned result) or let the call
|
|
* proceed.
|
|
*
|
|
* @see Instrumentation#addMonitor
|
|
*/
|
|
public ActivityMonitor(
|
|
IntentFilter which, ActivityResult result, boolean block) {
|
|
mWhich = which;
|
|
mClass = null;
|
|
mResult = result;
|
|
mBlock = block;
|
|
mIgnoreMatchingSpecificIntents = false;
|
|
}
|
|
|
|
/**
|
|
* Create a new ActivityMonitor that looks for a specific activity
|
|
* class to be started.
|
|
*
|
|
* @param cls The activity class this monitor is responsible for.
|
|
* @param result A canned result to return if the monitor is hit; can
|
|
* be null.
|
|
* @param block Controls whether the monitor should block the activity
|
|
* start (returning its canned result) or let the call
|
|
* proceed.
|
|
*
|
|
* @see Instrumentation#addMonitor
|
|
*/
|
|
public ActivityMonitor(
|
|
String cls, ActivityResult result, boolean block) {
|
|
mWhich = null;
|
|
mClass = cls;
|
|
mResult = result;
|
|
mBlock = block;
|
|
mIgnoreMatchingSpecificIntents = false;
|
|
}
|
|
|
|
/**
|
|
* Create a new ActivityMonitor that can be used for intercepting any activity to be
|
|
* started.
|
|
*
|
|
* <p> When an activity is started, {@link #onStartActivity(Intent)} will be called on
|
|
* instances created using this constructor to see if it is a hit.
|
|
*
|
|
* @see #onStartActivity(Intent)
|
|
*/
|
|
public ActivityMonitor() {
|
|
mWhich = null;
|
|
mClass = null;
|
|
mResult = null;
|
|
mBlock = false;
|
|
mIgnoreMatchingSpecificIntents = true;
|
|
}
|
|
|
|
/**
|
|
* @return true if this monitor is used for intercepting any started activity by calling
|
|
* into {@link #onStartActivity(Intent)}, false if this monitor is only used
|
|
* for specific intents corresponding to the intent filter or activity class
|
|
* passed in the constructor.
|
|
*/
|
|
final boolean ignoreMatchingSpecificIntents() {
|
|
return mIgnoreMatchingSpecificIntents;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the filter associated with this ActivityMonitor.
|
|
*/
|
|
public final IntentFilter getFilter() {
|
|
return mWhich;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the result associated with this ActivityMonitor, or null if
|
|
* none.
|
|
*/
|
|
public final ActivityResult getResult() {
|
|
return mResult;
|
|
}
|
|
|
|
/**
|
|
* Check whether this monitor blocks activity starts (not allowing the
|
|
* actual activity to run) or allows them to execute normally.
|
|
*/
|
|
public final boolean isBlocking() {
|
|
return mBlock;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the number of times the monitor has been hit so far.
|
|
*/
|
|
public final int getHits() {
|
|
return mHits;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the most recent activity class that was seen by this
|
|
* monitor.
|
|
*/
|
|
public final Activity getLastActivity() {
|
|
return mLastActivity;
|
|
}
|
|
|
|
/**
|
|
* Block until an Activity is created that matches this monitor,
|
|
* returning the resulting activity.
|
|
*
|
|
* @return Activity
|
|
*/
|
|
public final Activity waitForActivity() {
|
|
synchronized (this) {
|
|
while (mLastActivity == null) {
|
|
try {
|
|
wait();
|
|
} catch (InterruptedException e) {
|
|
}
|
|
}
|
|
Activity res = mLastActivity;
|
|
mLastActivity = null;
|
|
return res;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Block until an Activity is created that matches this monitor,
|
|
* returning the resulting activity or till the timeOut period expires.
|
|
* If the timeOut expires before the activity is started, return null.
|
|
*
|
|
* @param timeOut Time to wait in milliseconds before the activity is created.
|
|
*
|
|
* @return Activity
|
|
*/
|
|
public final Activity waitForActivityWithTimeout(long timeOut) {
|
|
synchronized (this) {
|
|
if (mLastActivity == null) {
|
|
try {
|
|
wait(timeOut);
|
|
} catch (InterruptedException e) {
|
|
}
|
|
}
|
|
if (mLastActivity == null) {
|
|
return null;
|
|
} else {
|
|
Activity res = mLastActivity;
|
|
mLastActivity = null;
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This overload is used for notifying the {@link android.window.TaskFragmentOrganizer}
|
|
* implementation internally about started activities.
|
|
*
|
|
* @see #onStartActivity(Intent)
|
|
* @hide
|
|
*/
|
|
public ActivityResult onStartActivity(@NonNull Context who, @NonNull Intent intent,
|
|
@NonNull Bundle options) {
|
|
return onStartActivity(intent);
|
|
}
|
|
|
|
/**
|
|
* Used for intercepting any started activity.
|
|
*
|
|
* <p> A non-null return value here will be considered a hit for this monitor.
|
|
* By default this will return {@code null} and subclasses can override this to return
|
|
* a non-null value if the intent needs to be intercepted.
|
|
*
|
|
* <p> Whenever a new activity is started, this method will be called on instances created
|
|
* using {@link #ActivityMonitor()} to check if there is a match. In case
|
|
* of a match, the activity start will be blocked and the returned result will be used.
|
|
*
|
|
* @param intent The intent used for starting the activity.
|
|
* @return The {@link ActivityResult} that needs to be used in case of a match.
|
|
*/
|
|
public ActivityResult onStartActivity(Intent intent) {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* This is called after starting an Activity and provides the result code that defined in
|
|
* {@link ActivityManager}, like {@link ActivityManager#START_SUCCESS}.
|
|
*
|
|
* @param result the result code that returns after starting an Activity.
|
|
* @param bOptions the bundle generated from {@link ActivityOptions} that originally
|
|
* being used to start the Activity.
|
|
* @hide
|
|
*/
|
|
public void onStartActivityResult(int result, @NonNull Bundle bOptions) {}
|
|
|
|
final boolean match(Context who,
|
|
Activity activity,
|
|
Intent intent) {
|
|
if (mIgnoreMatchingSpecificIntents) {
|
|
return false;
|
|
}
|
|
synchronized (this) {
|
|
if (mWhich != null
|
|
&& mWhich.match(who.getContentResolver(), intent,
|
|
true, "Instrumentation") < 0) {
|
|
return false;
|
|
}
|
|
if (mClass != null) {
|
|
String cls = null;
|
|
if (activity != null) {
|
|
cls = activity.getClass().getName();
|
|
} else if (intent.getComponent() != null) {
|
|
cls = intent.getComponent().getClassName();
|
|
}
|
|
if (cls == null || !mClass.equals(cls)) {
|
|
return false;
|
|
}
|
|
}
|
|
if (activity != null) {
|
|
mLastActivity = activity;
|
|
notifyAll();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a new {@link ActivityMonitor} that will be checked whenever an
|
|
* activity is started. The monitor is added
|
|
* after any existing ones; the monitor will be hit only if none of the
|
|
* existing monitors can themselves handle the Intent.
|
|
*
|
|
* @param monitor The new ActivityMonitor to see.
|
|
*
|
|
* @see #addMonitor(IntentFilter, ActivityResult, boolean)
|
|
* @see #checkMonitorHit
|
|
*/
|
|
public void addMonitor(ActivityMonitor monitor) {
|
|
synchronized (mSync) {
|
|
if (mActivityMonitors == null) {
|
|
mActivityMonitors = new ArrayList();
|
|
}
|
|
mActivityMonitors.add(monitor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A convenience wrapper for {@link #addMonitor(ActivityMonitor)} that
|
|
* creates an intent filter matching {@link ActivityMonitor} for you and
|
|
* returns it.
|
|
*
|
|
* @param filter The set of intents this monitor is responsible for.
|
|
* @param result A canned result to return if the monitor is hit; can
|
|
* be null.
|
|
* @param block Controls whether the monitor should block the activity
|
|
* start (returning its canned result) or let the call
|
|
* proceed.
|
|
*
|
|
* @return The newly created and added activity monitor.
|
|
*
|
|
* @see #addMonitor(ActivityMonitor)
|
|
* @see #checkMonitorHit
|
|
*/
|
|
public ActivityMonitor addMonitor(
|
|
IntentFilter filter, ActivityResult result, boolean block) {
|
|
ActivityMonitor am = new ActivityMonitor(filter, result, block);
|
|
addMonitor(am);
|
|
return am;
|
|
}
|
|
|
|
/**
|
|
* A convenience wrapper for {@link #addMonitor(ActivityMonitor)} that
|
|
* creates a class matching {@link ActivityMonitor} for you and returns it.
|
|
*
|
|
* @param cls The activity class this monitor is responsible for.
|
|
* @param result A canned result to return if the monitor is hit; can
|
|
* be null.
|
|
* @param block Controls whether the monitor should block the activity
|
|
* start (returning its canned result) or let the call
|
|
* proceed.
|
|
*
|
|
* @return The newly created and added activity monitor.
|
|
*
|
|
* @see #addMonitor(ActivityMonitor)
|
|
* @see #checkMonitorHit
|
|
*/
|
|
public ActivityMonitor addMonitor(
|
|
String cls, ActivityResult result, boolean block) {
|
|
ActivityMonitor am = new ActivityMonitor(cls, result, block);
|
|
addMonitor(am);
|
|
return am;
|
|
}
|
|
|
|
/**
|
|
* Test whether an existing {@link ActivityMonitor} has been hit. If the
|
|
* monitor has been hit at least <var>minHits</var> times, then it will be
|
|
* removed from the activity monitor list and true returned. Otherwise it
|
|
* is left as-is and false is returned.
|
|
*
|
|
* @param monitor The ActivityMonitor to check.
|
|
* @param minHits The minimum number of hits required.
|
|
*
|
|
* @return True if the hit count has been reached, else false.
|
|
*
|
|
* @see #addMonitor
|
|
*/
|
|
public boolean checkMonitorHit(ActivityMonitor monitor, int minHits) {
|
|
waitForIdleSync();
|
|
synchronized (mSync) {
|
|
if (monitor.getHits() < minHits) {
|
|
return false;
|
|
}
|
|
mActivityMonitors.remove(monitor);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Wait for an existing {@link ActivityMonitor} to be hit. Once the
|
|
* monitor has been hit, it is removed from the activity monitor list and
|
|
* the first created Activity object that matched it is returned.
|
|
*
|
|
* @param monitor The ActivityMonitor to wait for.
|
|
*
|
|
* @return The Activity object that matched the monitor.
|
|
*/
|
|
public Activity waitForMonitor(ActivityMonitor monitor) {
|
|
Activity activity = monitor.waitForActivity();
|
|
synchronized (mSync) {
|
|
mActivityMonitors.remove(monitor);
|
|
}
|
|
return activity;
|
|
}
|
|
|
|
/**
|
|
* Wait for an existing {@link ActivityMonitor} to be hit till the timeout
|
|
* expires. Once the monitor has been hit, it is removed from the activity
|
|
* monitor list and the first created Activity object that matched it is
|
|
* returned. If the timeout expires, a null object is returned.
|
|
*
|
|
* @param monitor The ActivityMonitor to wait for.
|
|
* @param timeOut The timeout value in milliseconds.
|
|
*
|
|
* @return The Activity object that matched the monitor.
|
|
*/
|
|
public Activity waitForMonitorWithTimeout(ActivityMonitor monitor, long timeOut) {
|
|
Activity activity = monitor.waitForActivityWithTimeout(timeOut);
|
|
synchronized (mSync) {
|
|
mActivityMonitors.remove(monitor);
|
|
}
|
|
return activity;
|
|
}
|
|
|
|
/**
|
|
* Remove an {@link ActivityMonitor} that was previously added with
|
|
* {@link #addMonitor}.
|
|
*
|
|
* @param monitor The monitor to remove.
|
|
*
|
|
* @see #addMonitor
|
|
*/
|
|
public void removeMonitor(ActivityMonitor monitor) {
|
|
synchronized (mSync) {
|
|
mActivityMonitors.remove(monitor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute a particular menu item.
|
|
*
|
|
* @param targetActivity The activity in question.
|
|
* @param id The identifier associated with the menu item.
|
|
* @param flag Additional flags, if any.
|
|
* @return Whether the invocation was successful (for example, it could be
|
|
* false if item is disabled).
|
|
*/
|
|
public boolean invokeMenuActionSync(Activity targetActivity,
|
|
int id, int flag) {
|
|
class MenuRunnable implements Runnable {
|
|
private final Activity activity;
|
|
private final int identifier;
|
|
private final int flags;
|
|
boolean returnValue;
|
|
|
|
public MenuRunnable(Activity _activity, int _identifier,
|
|
int _flags) {
|
|
activity = _activity;
|
|
identifier = _identifier;
|
|
flags = _flags;
|
|
}
|
|
|
|
public void run() {
|
|
Window win = activity.getWindow();
|
|
|
|
returnValue = win.performPanelIdentifierAction(
|
|
Window.FEATURE_OPTIONS_PANEL,
|
|
identifier,
|
|
flags);
|
|
}
|
|
|
|
}
|
|
MenuRunnable mr = new MenuRunnable(targetActivity, id, flag);
|
|
runOnMainSync(mr);
|
|
return mr.returnValue;
|
|
}
|
|
|
|
/**
|
|
* Show the context menu for the currently focused view and executes a
|
|
* particular context menu item.
|
|
*
|
|
* @param targetActivity The activity in question.
|
|
* @param id The identifier associated with the context menu item.
|
|
* @param flag Additional flags, if any.
|
|
* @return Whether the invocation was successful (for example, it could be
|
|
* false if item is disabled).
|
|
*/
|
|
public boolean invokeContextMenuAction(Activity targetActivity, int id, int flag) {
|
|
validateNotAppThread();
|
|
|
|
// Bring up context menu for current focus.
|
|
// It'd be nice to do this through code, but currently ListView depends on
|
|
// long press to set metadata for its selected child
|
|
|
|
final KeyEvent downEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER);
|
|
sendKeySync(downEvent);
|
|
|
|
// Need to wait for long press
|
|
waitForIdleSync();
|
|
try {
|
|
Thread.sleep(ViewConfiguration.getLongPressTimeout());
|
|
} catch (InterruptedException e) {
|
|
Log.e(TAG, "Could not sleep for long press timeout", e);
|
|
return false;
|
|
}
|
|
|
|
final KeyEvent upEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER);
|
|
sendKeySync(upEvent);
|
|
|
|
// Wait for context menu to appear
|
|
waitForIdleSync();
|
|
|
|
class ContextMenuRunnable implements Runnable {
|
|
private final Activity activity;
|
|
private final int identifier;
|
|
private final int flags;
|
|
boolean returnValue;
|
|
|
|
public ContextMenuRunnable(Activity _activity, int _identifier,
|
|
int _flags) {
|
|
activity = _activity;
|
|
identifier = _identifier;
|
|
flags = _flags;
|
|
}
|
|
|
|
public void run() {
|
|
Window win = activity.getWindow();
|
|
returnValue = win.performContextMenuIdentifierAction(
|
|
identifier,
|
|
flags);
|
|
}
|
|
|
|
}
|
|
|
|
ContextMenuRunnable cmr = new ContextMenuRunnable(targetActivity, id, flag);
|
|
runOnMainSync(cmr);
|
|
return cmr.returnValue;
|
|
}
|
|
|
|
/**
|
|
* Sends the key events that result in the given text being typed into the currently focused
|
|
* window, and waits for it to be processed.
|
|
*
|
|
* @param text The text to be sent.
|
|
* @see #sendKeySync(KeyEvent)
|
|
*/
|
|
public void sendStringSync(String text) {
|
|
if (text == null) {
|
|
return;
|
|
}
|
|
KeyCharacterMap keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
|
|
|
|
KeyEvent[] events = keyCharacterMap.getEvents(text.toCharArray());
|
|
|
|
if (events != null) {
|
|
for (int i = 0; i < events.length; i++) {
|
|
// We have to change the time of an event before injecting it because
|
|
// all KeyEvents returned by KeyCharacterMap.getEvents() have the same
|
|
// time stamp and the system rejects too old events. Hence, it is
|
|
// possible for an event to become stale before it is injected if it
|
|
// takes too long to inject the preceding ones.
|
|
sendKeySync(KeyEvent.changeTimeRepeat(events[i], SystemClock.uptimeMillis(), 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sends a key event to the currently focused window, and waits for it to be processed.
|
|
* <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 The event to send to the current focus.
|
|
*/
|
|
public void sendKeySync(KeyEvent event) {
|
|
validateNotAppThread();
|
|
|
|
long downTime = event.getDownTime();
|
|
long eventTime = event.getEventTime();
|
|
int source = event.getSource();
|
|
if (source == InputDevice.SOURCE_UNKNOWN) {
|
|
source = InputDevice.SOURCE_KEYBOARD;
|
|
}
|
|
if (eventTime == 0) {
|
|
eventTime = SystemClock.uptimeMillis();
|
|
}
|
|
if (downTime == 0) {
|
|
downTime = eventTime;
|
|
}
|
|
KeyEvent newEvent = new KeyEvent(event);
|
|
newEvent.setTime(downTime, eventTime);
|
|
newEvent.setSource(source);
|
|
newEvent.setFlags(event.getFlags() | KeyEvent.FLAG_FROM_SYSTEM);
|
|
setDisplayIfNeeded(newEvent);
|
|
|
|
InputManagerGlobal.getInstance().injectInputEvent(newEvent,
|
|
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
|
|
}
|
|
|
|
private void setDisplayIfNeeded(KeyEvent event) {
|
|
if (!UserManager.isVisibleBackgroundUsersEnabled()) {
|
|
return;
|
|
}
|
|
// In devices that support visible background users visible, the display id must be set to
|
|
// reflect the display the user was started visible on, otherwise the event would be sent to
|
|
// the main display (which would most likely fail the test).
|
|
int eventDisplayId = event.getDisplayId();
|
|
if (eventDisplayId != Display.INVALID_DISPLAY) {
|
|
if (VERBOSE) {
|
|
Log.v(TAG, "setDisplayIfNeeded(" + event + "): not changing display id as it's "
|
|
+ "explicitly set to " + eventDisplayId);
|
|
}
|
|
return;
|
|
}
|
|
|
|
UserManager userManager = mInstrContext.getSystemService(UserManager.class);
|
|
int userDisplayId = userManager.getMainDisplayIdAssignedToUser();
|
|
if (VERBOSE) {
|
|
Log.v(TAG, "setDisplayIfNeeded(" + event + "): eventDisplayId=" + eventDisplayId
|
|
+ ", user=" + mInstrContext.getUser() + ", userDisplayId=" + userDisplayId);
|
|
}
|
|
if (userDisplayId == Display.INVALID_DISPLAY) {
|
|
Log.e(TAG, "setDisplayIfNeeded(" + event + "): UserManager returned INVALID_DISPLAY as "
|
|
+ "display assigned to user " + mInstrContext.getUser());
|
|
return;
|
|
|
|
}
|
|
|
|
event.setDisplayId(userDisplayId);
|
|
}
|
|
|
|
/**
|
|
* Sends up and down key events with the given key code to the currently focused window, and
|
|
* waits for it to be processed.
|
|
*
|
|
* @param keyCode The key code for the events to send.
|
|
* @see #sendKeySync(KeyEvent)
|
|
*/
|
|
public void sendKeyDownUpSync(int keyCode) {
|
|
sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
|
|
sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
|
}
|
|
|
|
/**
|
|
* Sends up and down key events with the given key code to the currently focused window, and
|
|
* waits for it to be processed.
|
|
* <p>
|
|
* Equivalent to {@link #sendKeyDownUpSync(int)}.
|
|
*
|
|
* @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
|
|
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
|
|
* {@link SystemClock#uptimeMillis()} as the timebase.
|
|
*/
|
|
public void sendPointerSync(MotionEvent event) {
|
|
validateNotAppThread();
|
|
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
|
|
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
|
|
}
|
|
|
|
syncInputTransactionsAndInjectEventIntoSelf(event);
|
|
}
|
|
|
|
private void syncInputTransactionsAndInjectEventIntoSelf(MotionEvent event) {
|
|
final boolean syncBefore = event.getAction() == MotionEvent.ACTION_DOWN
|
|
|| event.isFromSource(InputDevice.SOURCE_MOUSE);
|
|
final boolean syncAfter = event.getAction() == MotionEvent.ACTION_UP;
|
|
|
|
try {
|
|
if (syncBefore) {
|
|
WindowManagerGlobal.getWindowManagerService()
|
|
.syncInputTransactions(true /*waitForAnimations*/);
|
|
}
|
|
|
|
// Direct the injected event into windows owned by the instrumentation target.
|
|
InputManagerGlobal.getInstance().injectInputEvent(
|
|
event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH, Process.myUid());
|
|
|
|
if (syncAfter) {
|
|
WindowManagerGlobal.getWindowManagerService()
|
|
.syncInputTransactions(true /*waitForAnimations*/);
|
|
}
|
|
} catch (RemoteException e) {
|
|
e.rethrowFromSystemServer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatches a trackball event into the currently focused window, and waits for it to be
|
|
* processed.
|
|
* <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 trackball action. (As noted in
|
|
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
|
|
* {@link SystemClock#uptimeMillis()} as the timebase.
|
|
*/
|
|
public void sendTrackballEventSync(MotionEvent event) {
|
|
validateNotAppThread();
|
|
if (!event.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
|
|
event.setSource(InputDevice.SOURCE_TRACKBALL);
|
|
}
|
|
InputManagerGlobal.getInstance().injectInputEvent(event,
|
|
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
|
|
}
|
|
|
|
/**
|
|
* Perform instantiation of the process's {@link Application} object. The
|
|
* default implementation provides the normal system behavior.
|
|
*
|
|
* @param cl The ClassLoader with which to instantiate the object.
|
|
* @param className The name of the class implementing the Application
|
|
* object.
|
|
* @param context The context to initialize the application with
|
|
*
|
|
* @return The newly instantiated Application object.
|
|
*/
|
|
public Application newApplication(ClassLoader cl, String className, Context context)
|
|
throws InstantiationException, IllegalAccessException,
|
|
ClassNotFoundException {
|
|
Application app = getFactory(context.getPackageName())
|
|
.instantiateApplication(cl, className);
|
|
app.attach(context);
|
|
return app;
|
|
}
|
|
|
|
/**
|
|
* Perform instantiation of the process's {@link Application} object. The
|
|
* default implementation provides the normal system behavior.
|
|
*
|
|
* @param clazz The class used to create an Application object from.
|
|
* @param context The context to initialize the application with
|
|
*
|
|
* @return The newly instantiated Application object.
|
|
*/
|
|
static public Application newApplication(Class<?> clazz, Context context)
|
|
throws InstantiationException, IllegalAccessException,
|
|
ClassNotFoundException {
|
|
Application app = (Application)clazz.newInstance();
|
|
app.attach(context);
|
|
return app;
|
|
}
|
|
|
|
/**
|
|
* Perform calling of the application's {@link Application#onCreate}
|
|
* method. The default implementation simply calls through to that method.
|
|
*
|
|
* <p>Note: This method will be called immediately after {@link #onCreate(Bundle)}.
|
|
* Often instrumentation tests start their test thread in onCreate(); you
|
|
* need to be careful of races between these. (Well between it and
|
|
* everything else, but let's start here.)
|
|
*
|
|
* @param app The application being created.
|
|
*/
|
|
public void callApplicationOnCreate(Application app) {
|
|
app.onCreate();
|
|
}
|
|
|
|
/**
|
|
* Perform instantiation of an {@link Activity} object. This method is intended for use with
|
|
* unit tests, such as android.test.ActivityUnitTestCase. The activity will be useable
|
|
* locally but will be missing some of the linkages necessary for use within the system.
|
|
*
|
|
* @param clazz The Class of the desired Activity
|
|
* @param context The base context for the activity to use
|
|
* @param token The token for this activity to communicate with
|
|
* @param application The application object (if any)
|
|
* @param intent The intent that started this Activity
|
|
* @param info ActivityInfo from the manifest
|
|
* @param title The title, typically retrieved from the ActivityInfo record
|
|
* @param parent The parent Activity (if any)
|
|
* @param id The embedded Id (if any)
|
|
* @param lastNonConfigurationInstance Arbitrary object that will be
|
|
* available via {@link Activity#getLastNonConfigurationInstance()
|
|
* Activity.getLastNonConfigurationInstance()}.
|
|
* @return Returns the instantiated activity
|
|
* @throws InstantiationException
|
|
* @throws IllegalAccessException
|
|
*/
|
|
public Activity newActivity(Class<?> clazz, Context context,
|
|
IBinder token, Application application, Intent intent, ActivityInfo info,
|
|
CharSequence title, Activity parent, String id,
|
|
Object lastNonConfigurationInstance) throws InstantiationException,
|
|
IllegalAccessException {
|
|
Activity activity = (Activity)clazz.newInstance();
|
|
ActivityThread aThread = null;
|
|
// Activity.attach expects a non-null Application Object.
|
|
if (application == null) {
|
|
application = new Application();
|
|
}
|
|
activity.attach(context, aThread, this, token, 0 /* ident */, application, intent,
|
|
info, title, parent, id,
|
|
(Activity.NonConfigurationInstances)lastNonConfigurationInstance,
|
|
new Configuration(), null /* referrer */, null /* voiceInteractor */,
|
|
null /* window */, null /* activityCallback */, null /* assistToken */,
|
|
null /* shareableActivityToken */, null /* initialCallerInfoAccessToken */);
|
|
return activity;
|
|
}
|
|
|
|
/**
|
|
* Perform instantiation of the process's {@link Activity} object. The
|
|
* default implementation provides the normal system behavior.
|
|
*
|
|
* @param cl The ClassLoader with which to instantiate the object.
|
|
* @param className The name of the class implementing the Activity
|
|
* object.
|
|
* @param intent The Intent object that specified the activity class being
|
|
* instantiated.
|
|
*
|
|
* @return The newly instantiated Activity object.
|
|
*/
|
|
public Activity newActivity(ClassLoader cl, String className,
|
|
Intent intent)
|
|
throws InstantiationException, IllegalAccessException,
|
|
ClassNotFoundException {
|
|
String pkg = intent != null && intent.getComponent() != null
|
|
? intent.getComponent().getPackageName() : null;
|
|
return getFactory(pkg).instantiateActivity(cl, className, intent);
|
|
}
|
|
|
|
private AppComponentFactory getFactory(String pkg) {
|
|
if (pkg == null) {
|
|
Log.e(TAG, "No pkg specified, disabling AppComponentFactory");
|
|
return AppComponentFactory.DEFAULT;
|
|
}
|
|
if (mThread == null) {
|
|
Log.e(TAG, "Uninitialized ActivityThread, likely app-created Instrumentation,"
|
|
+ " disabling AppComponentFactory", new Throwable());
|
|
return AppComponentFactory.DEFAULT;
|
|
}
|
|
LoadedApk apk = mThread.peekPackageInfo(pkg, true);
|
|
// This is in the case of starting up "android".
|
|
if (apk == null) apk = mThread.getSystemContext().mPackageInfo;
|
|
return apk.getAppFactory();
|
|
}
|
|
|
|
/**
|
|
* This should be called before {@link #checkStartActivityResult(int, Object)}, because
|
|
* exceptions might be thrown while checking the results.
|
|
*/
|
|
private void notifyStartActivityResult(int result, @Nullable Bundle options) {
|
|
if (mActivityMonitors == null) {
|
|
return;
|
|
}
|
|
synchronized (mSync) {
|
|
final int size = mActivityMonitors.size();
|
|
for (int i = 0; i < size; i++) {
|
|
final ActivityMonitor am = mActivityMonitors.get(i);
|
|
if (am.ignoreMatchingSpecificIntents()) {
|
|
if (options == null) {
|
|
options = ActivityOptions.makeBasic().toBundle();
|
|
}
|
|
am.onStartActivityResult(result, options);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void prePerformCreate(Activity activity) {
|
|
if (mWaitingActivities != null) {
|
|
synchronized (mSync) {
|
|
final int N = mWaitingActivities.size();
|
|
for (int i=0; i<N; i++) {
|
|
final ActivityWaiter aw = mWaitingActivities.get(i);
|
|
final Intent intent = aw.intent;
|
|
if (intent.filterEquals(activity.getIntent())) {
|
|
aw.activity = activity;
|
|
mMessageQueue.addIdleHandler(new ActivityGoing(aw));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void postPerformCreate(Activity activity) {
|
|
if (mActivityMonitors != null) {
|
|
synchronized (mSync) {
|
|
final int N = mActivityMonitors.size();
|
|
for (int i=0; i<N; i++) {
|
|
final ActivityMonitor am = mActivityMonitors.get(i);
|
|
am.match(activity, activity, activity.getIntent());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onCreate}
|
|
* method. The default implementation simply calls through to that method.
|
|
*
|
|
* @param activity The activity being created.
|
|
* @param icicle The previously frozen state (or null) to pass through to onCreate().
|
|
*/
|
|
public void callActivityOnCreate(Activity activity, Bundle icicle) {
|
|
prePerformCreate(activity);
|
|
activity.performCreate(icicle);
|
|
postPerformCreate(activity);
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onCreate}
|
|
* method. The default implementation simply calls through to that method.
|
|
* @param activity The activity being created.
|
|
* @param icicle The previously frozen state (or null) to pass through to
|
|
* @param persistentState The previously persisted state (or null)
|
|
*/
|
|
public void callActivityOnCreate(Activity activity, Bundle icicle,
|
|
PersistableBundle persistentState) {
|
|
prePerformCreate(activity);
|
|
activity.performCreate(icicle, persistentState);
|
|
postPerformCreate(activity);
|
|
}
|
|
|
|
public void callActivityOnDestroy(Activity activity) {
|
|
// TODO: the following block causes intermittent hangs when using startActivity
|
|
// temporarily comment out until root cause is fixed (bug 2630683)
|
|
// if (mWaitingActivities != null) {
|
|
// synchronized (mSync) {
|
|
// final int N = mWaitingActivities.size();
|
|
// for (int i=0; i<N; i++) {
|
|
// final ActivityWaiter aw = mWaitingActivities.get(i);
|
|
// final Intent intent = aw.intent;
|
|
// if (intent.filterEquals(activity.getIntent())) {
|
|
// aw.activity = activity;
|
|
// mMessageQueue.addIdleHandler(new ActivityGoing(aw));
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
activity.performDestroy();
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onRestoreInstanceState}
|
|
* method. The default implementation simply calls through to that method.
|
|
*
|
|
* @param activity The activity being restored.
|
|
* @param savedInstanceState The previously saved state being restored.
|
|
*/
|
|
public void callActivityOnRestoreInstanceState(@NonNull Activity activity,
|
|
@NonNull Bundle savedInstanceState) {
|
|
activity.performRestoreInstanceState(savedInstanceState);
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onRestoreInstanceState}
|
|
* method. The default implementation simply calls through to that method.
|
|
*
|
|
* @param activity The activity being restored.
|
|
* @param savedInstanceState The previously saved state being restored (or null).
|
|
* @param persistentState The previously persisted state (or null)
|
|
*/
|
|
public void callActivityOnRestoreInstanceState(@NonNull Activity activity,
|
|
@Nullable Bundle savedInstanceState,
|
|
@Nullable PersistableBundle persistentState) {
|
|
activity.performRestoreInstanceState(savedInstanceState, persistentState);
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onPostCreate} method.
|
|
* The default implementation simply calls through to that method.
|
|
*
|
|
* @param activity The activity being created.
|
|
* @param savedInstanceState The previously saved state (or null) to pass through to
|
|
* onPostCreate().
|
|
*/
|
|
public void callActivityOnPostCreate(@NonNull Activity activity,
|
|
@Nullable Bundle savedInstanceState) {
|
|
activity.onPostCreate(savedInstanceState);
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onPostCreate} method.
|
|
* The default implementation simply calls through to that method.
|
|
*
|
|
* @param activity The activity being created.
|
|
* @param savedInstanceState The previously frozen state (or null) to pass through to
|
|
* onPostCreate().
|
|
* @param persistentState The previously persisted state (or null)
|
|
*/
|
|
public void callActivityOnPostCreate(@NonNull Activity activity,
|
|
@Nullable Bundle savedInstanceState,
|
|
@Nullable PersistableBundle persistentState) {
|
|
activity.onPostCreate(savedInstanceState, persistentState);
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onNewIntent}
|
|
* method. The default implementation simply calls through to that method.
|
|
*
|
|
* @param activity The activity receiving a new Intent.
|
|
* @param intent The new intent being received.
|
|
*/
|
|
public void callActivityOnNewIntent(Activity activity, Intent intent) {
|
|
if (android.security.Flags.contentUriPermissionApis()) {
|
|
activity.performNewIntent(intent, new ComponentCaller(activity.getActivityToken(),
|
|
/* callerToken */ null));
|
|
} else {
|
|
activity.performNewIntent(intent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Same as {@link #callActivityOnNewIntent(Activity, Intent)}, but with an extra parameter for
|
|
* the {@link ComponentCaller} instance associated with the app that sent the intent.
|
|
*
|
|
* @param activity The activity receiving a new Intent.
|
|
* @param intent The new intent being received.
|
|
* @param caller The {@link ComponentCaller} instance that launched the activity with the new
|
|
* intent.
|
|
*/
|
|
@FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
|
|
public void callActivityOnNewIntent(@NonNull Activity activity, @NonNull Intent intent,
|
|
@NonNull ComponentCaller caller) {
|
|
activity.performNewIntent(intent, caller);
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
|
|
public void callActivityOnNewIntent(Activity activity, ReferrerIntent intent,
|
|
@NonNull ComponentCaller caller) {
|
|
internalCallActivityOnNewIntent(activity, intent, caller);
|
|
}
|
|
|
|
@FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
|
|
private void internalCallActivityOnNewIntent(Activity activity, ReferrerIntent intent,
|
|
@NonNull ComponentCaller caller) {
|
|
final String oldReferrer = activity.mReferrer;
|
|
try {
|
|
if (intent != null) {
|
|
activity.mReferrer = intent.mReferrer;
|
|
}
|
|
Intent newIntent = intent != null ? new Intent(intent) : null;
|
|
callActivityOnNewIntent(activity, newIntent, caller);
|
|
} finally {
|
|
activity.mReferrer = oldReferrer;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public void callActivityOnNewIntent(Activity activity, ReferrerIntent intent) {
|
|
if (android.security.Flags.contentUriPermissionApis()) {
|
|
internalCallActivityOnNewIntent(activity, intent, new ComponentCaller(
|
|
activity.getActivityToken(), /* callerToken */ null));
|
|
} else {
|
|
final String oldReferrer = activity.mReferrer;
|
|
try {
|
|
if (intent != null) {
|
|
activity.mReferrer = intent.mReferrer;
|
|
}
|
|
callActivityOnNewIntent(activity, intent != null ? new Intent(intent) : null);
|
|
} finally {
|
|
activity.mReferrer = oldReferrer;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onStart}
|
|
* method. The default implementation simply calls through to that method.
|
|
*
|
|
* @param activity The activity being started.
|
|
*/
|
|
public void callActivityOnStart(Activity activity) {
|
|
activity.onStart();
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onRestart}
|
|
* method. The default implementation simply calls through to that method.
|
|
*
|
|
* @param activity The activity being restarted.
|
|
*/
|
|
public void callActivityOnRestart(Activity activity) {
|
|
activity.onRestart();
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onResume} method. The
|
|
* default implementation simply calls through to that method.
|
|
*
|
|
* @param activity The activity being resumed.
|
|
*/
|
|
public void callActivityOnResume(Activity activity) {
|
|
activity.mResumed = true;
|
|
activity.onResume();
|
|
|
|
if (mActivityMonitors != null) {
|
|
synchronized (mSync) {
|
|
final int N = mActivityMonitors.size();
|
|
for (int i=0; i<N; i++) {
|
|
final ActivityMonitor am = mActivityMonitors.get(i);
|
|
am.match(activity, activity, activity.getIntent());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onStop}
|
|
* method. The default implementation simply calls through to that method.
|
|
*
|
|
* @param activity The activity being stopped.
|
|
*/
|
|
public void callActivityOnStop(Activity activity) {
|
|
activity.onStop();
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onSaveInstanceState}
|
|
* method. The default implementation simply calls through to that method.
|
|
*
|
|
* @param activity The activity being saved.
|
|
* @param outState The bundle to pass to the call.
|
|
*/
|
|
public void callActivityOnSaveInstanceState(@NonNull Activity activity,
|
|
@NonNull Bundle outState) {
|
|
activity.performSaveInstanceState(outState);
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onSaveInstanceState}
|
|
* method. The default implementation simply calls through to that method.
|
|
* @param activity The activity being saved.
|
|
* @param outState The bundle to pass to the call.
|
|
* @param outPersistentState The persistent bundle to pass to the call.
|
|
*/
|
|
public void callActivityOnSaveInstanceState(@NonNull Activity activity,
|
|
@NonNull Bundle outState, @NonNull PersistableBundle outPersistentState) {
|
|
activity.performSaveInstanceState(outState, outPersistentState);
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onPause} method. The
|
|
* default implementation simply calls through to that method.
|
|
*
|
|
* @param activity The activity being paused.
|
|
*/
|
|
public void callActivityOnPause(Activity activity) {
|
|
activity.performPause();
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onUserLeaveHint} method.
|
|
* The default implementation simply calls through to that method.
|
|
*
|
|
* @param activity The activity being notified that the user has navigated away
|
|
*/
|
|
public void callActivityOnUserLeaving(Activity activity) {
|
|
activity.performUserLeaving();
|
|
}
|
|
|
|
/**
|
|
* Perform calling of an activity's {@link Activity#onPictureInPictureRequested} method.
|
|
* The default implementation simply calls through to that method.
|
|
*
|
|
* @param activity The activity being notified that picture-in-picture is being requested.
|
|
*/
|
|
public void callActivityOnPictureInPictureRequested(@NonNull Activity activity) {
|
|
activity.onPictureInPictureRequested();
|
|
}
|
|
|
|
/*
|
|
* Starts allocation counting. This triggers a gc and resets the counts.
|
|
*
|
|
* @deprecated Accurate counting is a burden on the runtime and may be removed.
|
|
*/
|
|
@Deprecated
|
|
public void startAllocCounting() {
|
|
// Before we start trigger a GC and reset the debug counts. Run the
|
|
// finalizers and another GC before starting and stopping the alloc
|
|
// counts. This will free up any objects that were just sitting around
|
|
// waiting for their finalizers to be run.
|
|
Runtime.getRuntime().gc();
|
|
Runtime.getRuntime().runFinalization();
|
|
Runtime.getRuntime().gc();
|
|
|
|
Debug.resetAllCounts();
|
|
|
|
// start the counts
|
|
Debug.startAllocCounting();
|
|
}
|
|
|
|
/*
|
|
* Stops allocation counting.
|
|
*
|
|
* @deprecated Accurate counting is a burden on the runtime and may be removed.
|
|
*/
|
|
@Deprecated
|
|
public void stopAllocCounting() {
|
|
Runtime.getRuntime().gc();
|
|
Runtime.getRuntime().runFinalization();
|
|
Runtime.getRuntime().gc();
|
|
Debug.stopAllocCounting();
|
|
}
|
|
|
|
/**
|
|
* If Results already contains Key, it appends Value to the key's ArrayList
|
|
* associated with the key. If the key doesn't already exist in results, it
|
|
* adds the key/value pair to results.
|
|
*/
|
|
private void addValue(String key, int value, Bundle results) {
|
|
if (results.containsKey(key)) {
|
|
List<Integer> list = results.getIntegerArrayList(key);
|
|
if (list != null) {
|
|
list.add(value);
|
|
}
|
|
} else {
|
|
ArrayList<Integer> list = new ArrayList<Integer>();
|
|
list.add(value);
|
|
results.putIntegerArrayList(key, list);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a bundle with the current results from the allocation counting.
|
|
*/
|
|
public Bundle getAllocCounts() {
|
|
Bundle results = new Bundle();
|
|
results.putLong("global_alloc_count", Debug.getGlobalAllocCount());
|
|
results.putLong("global_alloc_size", Debug.getGlobalAllocSize());
|
|
results.putLong("global_freed_count", Debug.getGlobalFreedCount());
|
|
results.putLong("global_freed_size", Debug.getGlobalFreedSize());
|
|
results.putLong("gc_invocation_count", Debug.getGlobalGcInvocationCount());
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Returns a bundle with the counts for various binder counts for this process. Currently the only two that are
|
|
* reported are the number of send and the number of received transactions.
|
|
*/
|
|
public Bundle getBinderCounts() {
|
|
Bundle results = new Bundle();
|
|
results.putLong("sent_transactions", Debug.getBinderSentTransactions());
|
|
results.putLong("received_transactions", Debug.getBinderReceivedTransactions());
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Description of a Activity execution result to return to the original
|
|
* activity.
|
|
*/
|
|
public static final class ActivityResult {
|
|
/**
|
|
* Create a new activity result. See {@link Activity#setResult} for
|
|
* more information.
|
|
*
|
|
* @param resultCode The result code to propagate back to the
|
|
* originating activity, often RESULT_CANCELED or RESULT_OK
|
|
* @param resultData The data to propagate back to the originating
|
|
* activity.
|
|
*/
|
|
public ActivityResult(int resultCode, Intent resultData) {
|
|
mResultCode = resultCode;
|
|
mResultData = resultData;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the result code contained in this result.
|
|
*/
|
|
public int getResultCode() {
|
|
return mResultCode;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the data contained in this result.
|
|
*/
|
|
public Intent getResultData() {
|
|
return mResultData;
|
|
}
|
|
|
|
private final int mResultCode;
|
|
private final Intent mResultData;
|
|
}
|
|
|
|
/**
|
|
* Execute a startActivity call made by the application. The default
|
|
* implementation takes care of updating any active {@link ActivityMonitor}
|
|
* objects and dispatches this call to the system activity manager; you can
|
|
* override this to watch for the application to start an activity, and
|
|
* modify what happens when it does.
|
|
*
|
|
* <p>This method returns an {@link ActivityResult} object, which you can
|
|
* use when intercepting application calls to avoid performing the start
|
|
* activity action but still return the result the application is
|
|
* expecting. To do this, override this method to catch the call to start
|
|
* activity so that it returns a new ActivityResult containing the results
|
|
* you would like the application to see, and don't call up to the super
|
|
* class. Note that an application is only expecting a result if
|
|
* <var>requestCode</var> is >= 0.
|
|
*
|
|
* <p>This method throws {@link android.content.ActivityNotFoundException}
|
|
* if there was no Activity found to run the given Intent.
|
|
*
|
|
* @param who The Context from which the activity is being started.
|
|
* @param contextThread The main thread of the Context from which the activity
|
|
* is being started.
|
|
* @param token Internal token identifying to the system who is starting
|
|
* the activity; may be null.
|
|
* @param target Which activity is performing the start (and thus receiving
|
|
* any result); may be null if this call is not being made
|
|
* from an activity.
|
|
* @param intent The actual Intent to start.
|
|
* @param requestCode Identifier for this request's result; less than zero
|
|
* if the caller is not expecting a result.
|
|
* @param options Addition options.
|
|
*
|
|
* @return To force the return of a particular result, return an
|
|
* ActivityResult object containing the desired data; otherwise
|
|
* return null. The default implementation always returns null.
|
|
*
|
|
* @throws android.content.ActivityNotFoundException
|
|
*
|
|
* @see Activity#startActivity(Intent)
|
|
* @see Activity#startActivityForResult(Intent, int)
|
|
*
|
|
* {@hide}
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public ActivityResult execStartActivity(
|
|
Context who, IBinder contextThread, IBinder token, Activity target,
|
|
Intent intent, int requestCode, Bundle options) {
|
|
if (DEBUG_START_ACTIVITY) {
|
|
Log.d(TAG, "startActivity: who=" + who + " source=" + target + " intent=" + intent
|
|
+ " requestCode=" + requestCode + " options=" + options, new Throwable());
|
|
}
|
|
Objects.requireNonNull(intent);
|
|
IApplicationThread whoThread = (IApplicationThread) contextThread;
|
|
Uri referrer = target != null ? target.onProvideReferrer() : null;
|
|
if (referrer != null) {
|
|
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
|
|
}
|
|
if (isSdkSandboxAllowedToStartActivities()) {
|
|
adjustIntentForCtsInSdkSandboxInstrumentation(intent);
|
|
}
|
|
if (mActivityMonitors != null) {
|
|
synchronized (mSync) {
|
|
final int N = mActivityMonitors.size();
|
|
for (int i=0; i<N; i++) {
|
|
final ActivityMonitor am = mActivityMonitors.get(i);
|
|
ActivityResult result = null;
|
|
if (am.ignoreMatchingSpecificIntents()) {
|
|
if (options == null) {
|
|
options = ActivityOptions.makeBasic().toBundle();
|
|
}
|
|
result = am.onStartActivity(who, intent, options);
|
|
}
|
|
if (result != null) {
|
|
am.mHits++;
|
|
return result;
|
|
} else if (am.match(who, null, intent)) {
|
|
am.mHits++;
|
|
if (am.isBlocking()) {
|
|
return requestCode >= 0 ? am.getResult() : null;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
intent.migrateExtraStreamToClipData(who);
|
|
intent.prepareToLeaveProcess(who);
|
|
int result = ActivityTaskManager.getService().startActivity(whoThread,
|
|
who.getOpPackageName(), who.getAttributionTag(), intent,
|
|
intent.resolveTypeIfNeeded(who.getContentResolver()), token,
|
|
target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
|
|
notifyStartActivityResult(result, options);
|
|
checkStartActivityResult(result, intent);
|
|
} catch (RemoteException e) {
|
|
throw new RuntimeException("Failure from system", e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int, Bundle)},
|
|
* but accepts an array of activities to be started. Note that active
|
|
* {@link ActivityMonitor} objects only match against the first activity in
|
|
* the array.
|
|
*
|
|
* {@hide}
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public void execStartActivities(Context who, IBinder contextThread,
|
|
IBinder token, Activity target, Intent[] intents, Bundle options) {
|
|
execStartActivitiesAsUser(who, contextThread, token, target, intents, options,
|
|
who.getUserId());
|
|
}
|
|
|
|
/**
|
|
* Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int, Bundle)},
|
|
* but accepts an array of activities to be started. Note that active
|
|
* {@link ActivityMonitor} objects only match against the first activity in
|
|
* the array.
|
|
*
|
|
* @return The corresponding flag {@link ActivityManager#START_CANCELED},
|
|
* {@link ActivityManager#START_SUCCESS} etc. indicating whether the launch was
|
|
* successful.
|
|
*
|
|
* {@hide}
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public int execStartActivitiesAsUser(Context who, IBinder contextThread,
|
|
IBinder token, Activity target, Intent[] intents, Bundle options,
|
|
int userId) {
|
|
if (DEBUG_START_ACTIVITY) {
|
|
StringJoiner joiner = new StringJoiner(", ");
|
|
for (Intent i : intents) {
|
|
joiner.add(i.toString());
|
|
}
|
|
Log.d(TAG, "startActivities: who=" + who + " source=" + target + " userId=" + userId
|
|
+ " intents=[" + joiner + "] options=" + options, new Throwable());
|
|
}
|
|
Objects.requireNonNull(intents);
|
|
for (int i = intents.length - 1; i >= 0; i--) {
|
|
Objects.requireNonNull(intents[i]);
|
|
}
|
|
IApplicationThread whoThread = (IApplicationThread) contextThread;
|
|
if (isSdkSandboxAllowedToStartActivities()) {
|
|
for (Intent intent : intents) {
|
|
adjustIntentForCtsInSdkSandboxInstrumentation(intent);
|
|
}
|
|
}
|
|
if (mActivityMonitors != null) {
|
|
synchronized (mSync) {
|
|
final int N = mActivityMonitors.size();
|
|
for (int i=0; i<N; i++) {
|
|
final ActivityMonitor am = mActivityMonitors.get(i);
|
|
ActivityResult result = null;
|
|
if (am.ignoreMatchingSpecificIntents()) {
|
|
if (options == null) {
|
|
options = ActivityOptions.makeBasic().toBundle();
|
|
}
|
|
result = am.onStartActivity(who, intents[0], options);
|
|
}
|
|
if (result != null) {
|
|
am.mHits++;
|
|
return ActivityManager.START_CANCELED;
|
|
} else if (am.match(who, null, intents[0])) {
|
|
am.mHits++;
|
|
if (am.isBlocking()) {
|
|
return ActivityManager.START_CANCELED;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
String[] resolvedTypes = new String[intents.length];
|
|
for (int i=0; i<intents.length; i++) {
|
|
intents[i].migrateExtraStreamToClipData(who);
|
|
intents[i].prepareToLeaveProcess(who);
|
|
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver());
|
|
}
|
|
int result = ActivityTaskManager.getService().startActivities(whoThread,
|
|
who.getOpPackageName(), who.getAttributionTag(), intents, resolvedTypes,
|
|
token, options, userId);
|
|
notifyStartActivityResult(result, options);
|
|
checkStartActivityResult(result, intents[0]);
|
|
return result;
|
|
} catch (RemoteException e) {
|
|
throw new RuntimeException("Failure from system", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Like {@link #execStartActivity(android.content.Context, android.os.IBinder,
|
|
* android.os.IBinder, String, android.content.Intent, int, android.os.Bundle)},
|
|
* but for calls from a {@link Fragment}.
|
|
*
|
|
* @param who The Context from which the activity is being started.
|
|
* @param contextThread The main thread of the Context from which the activity
|
|
* is being started.
|
|
* @param token Internal token identifying to the system who is starting
|
|
* the activity; may be null.
|
|
* @param target Which element is performing the start (and thus receiving
|
|
* any result).
|
|
* @param intent The actual Intent to start.
|
|
* @param requestCode Identifier for this request's result; less than zero
|
|
* if the caller is not expecting a result.
|
|
*
|
|
* @return To force the return of a particular result, return an
|
|
* ActivityResult object containing the desired data; otherwise
|
|
* return null. The default implementation always returns null.
|
|
*
|
|
* @throws android.content.ActivityNotFoundException
|
|
*
|
|
* @see Activity#startActivity(Intent)
|
|
* @see Activity#startActivityForResult(Intent, int)
|
|
*
|
|
* {@hide}
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public ActivityResult execStartActivity(
|
|
Context who, IBinder contextThread, IBinder token, String target,
|
|
Intent intent, int requestCode, Bundle options) {
|
|
if (DEBUG_START_ACTIVITY) {
|
|
Log.d(TAG, "startActivity: who=" + who + " target=" + target
|
|
+ " intent=" + intent + " requestCode=" + requestCode
|
|
+ " options=" + options, new Throwable());
|
|
}
|
|
Objects.requireNonNull(intent);
|
|
IApplicationThread whoThread = (IApplicationThread) contextThread;
|
|
if (isSdkSandboxAllowedToStartActivities()) {
|
|
adjustIntentForCtsInSdkSandboxInstrumentation(intent);
|
|
}
|
|
if (mActivityMonitors != null) {
|
|
synchronized (mSync) {
|
|
final int N = mActivityMonitors.size();
|
|
for (int i=0; i<N; i++) {
|
|
final ActivityMonitor am = mActivityMonitors.get(i);
|
|
ActivityResult result = null;
|
|
if (am.ignoreMatchingSpecificIntents()) {
|
|
if (options == null) {
|
|
options = ActivityOptions.makeBasic().toBundle();
|
|
}
|
|
result = am.onStartActivity(who, intent, options);
|
|
}
|
|
if (result != null) {
|
|
am.mHits++;
|
|
return result;
|
|
} else if (am.match(who, null, intent)) {
|
|
am.mHits++;
|
|
if (am.isBlocking()) {
|
|
return requestCode >= 0 ? am.getResult() : null;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
intent.migrateExtraStreamToClipData(who);
|
|
intent.prepareToLeaveProcess(who);
|
|
int result = ActivityTaskManager.getService().startActivity(whoThread,
|
|
who.getOpPackageName(), who.getAttributionTag(), intent,
|
|
intent.resolveTypeIfNeeded(who.getContentResolver()), token, target,
|
|
requestCode, 0, null, options);
|
|
notifyStartActivityResult(result, options);
|
|
checkStartActivityResult(result, intent);
|
|
} catch (RemoteException e) {
|
|
throw new RuntimeException("Failure from system", e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int, Bundle)},
|
|
* but for starting as a particular user.
|
|
*
|
|
* @param who The Context from which the activity is being started.
|
|
* @param contextThread The main thread of the Context from which the activity
|
|
* is being started.
|
|
* @param token Internal token identifying to the system who is starting
|
|
* the activity; may be null.
|
|
* @param target Which fragment is performing the start (and thus receiving
|
|
* any result).
|
|
* @param intent The actual Intent to start.
|
|
* @param requestCode Identifier for this request's result; less than zero
|
|
* if the caller is not expecting a result.
|
|
*
|
|
* @return To force the return of a particular result, return an
|
|
* ActivityResult object containing the desired data; otherwise
|
|
* return null. The default implementation always returns null.
|
|
*
|
|
* @throws android.content.ActivityNotFoundException
|
|
*
|
|
* @see Activity#startActivity(Intent)
|
|
* @see Activity#startActivityForResult(Intent, int)
|
|
*
|
|
* {@hide}
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public ActivityResult execStartActivity(
|
|
Context who, IBinder contextThread, IBinder token, String resultWho,
|
|
Intent intent, int requestCode, Bundle options, UserHandle user) {
|
|
if (DEBUG_START_ACTIVITY) {
|
|
Log.d(TAG, "startActivity: who=" + who + " user=" + user + " intent=" + intent
|
|
+ " requestCode=" + requestCode + " resultWho=" + resultWho
|
|
+ " options=" + options, new Throwable());
|
|
}
|
|
Objects.requireNonNull(intent);
|
|
IApplicationThread whoThread = (IApplicationThread) contextThread;
|
|
if (isSdkSandboxAllowedToStartActivities()) {
|
|
adjustIntentForCtsInSdkSandboxInstrumentation(intent);
|
|
}
|
|
if (mActivityMonitors != null) {
|
|
synchronized (mSync) {
|
|
final int N = mActivityMonitors.size();
|
|
for (int i=0; i<N; i++) {
|
|
final ActivityMonitor am = mActivityMonitors.get(i);
|
|
ActivityResult result = null;
|
|
if (am.ignoreMatchingSpecificIntents()) {
|
|
if (options == null) {
|
|
options = ActivityOptions.makeBasic().toBundle();
|
|
}
|
|
result = am.onStartActivity(who, intent, options);
|
|
}
|
|
if (result != null) {
|
|
am.mHits++;
|
|
return result;
|
|
} else if (am.match(who, null, intent)) {
|
|
am.mHits++;
|
|
if (am.isBlocking()) {
|
|
return requestCode >= 0 ? am.getResult() : null;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
intent.migrateExtraStreamToClipData(who);
|
|
intent.prepareToLeaveProcess(who);
|
|
int result = ActivityTaskManager.getService().startActivityAsUser(whoThread,
|
|
who.getOpPackageName(), who.getAttributionTag(), intent,
|
|
intent.resolveTypeIfNeeded(who.getContentResolver()), token, resultWho,
|
|
requestCode, 0, null, options, user.getIdentifier());
|
|
notifyStartActivityResult(result, options);
|
|
checkStartActivityResult(result, intent);
|
|
} catch (RemoteException e) {
|
|
throw new RuntimeException("Failure from system", e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Special version!
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public ActivityResult execStartActivityAsCaller(
|
|
Context who, IBinder contextThread, IBinder token, Activity target,
|
|
Intent intent, int requestCode, Bundle options,
|
|
boolean ignoreTargetSecurity, int userId) {
|
|
if (DEBUG_START_ACTIVITY) {
|
|
Log.d(TAG, "startActivity: who=" + who + " source=" + target + " userId=" + userId
|
|
+ " intent=" + intent + " requestCode=" + requestCode
|
|
+ " ignoreTargetSecurity=" + ignoreTargetSecurity + " options=" + options,
|
|
new Throwable());
|
|
}
|
|
Objects.requireNonNull(intent);
|
|
IApplicationThread whoThread = (IApplicationThread) contextThread;
|
|
if (isSdkSandboxAllowedToStartActivities()) {
|
|
adjustIntentForCtsInSdkSandboxInstrumentation(intent);
|
|
}
|
|
if (mActivityMonitors != null) {
|
|
synchronized (mSync) {
|
|
final int N = mActivityMonitors.size();
|
|
for (int i=0; i<N; i++) {
|
|
final ActivityMonitor am = mActivityMonitors.get(i);
|
|
ActivityResult result = null;
|
|
if (am.ignoreMatchingSpecificIntents()) {
|
|
if (options == null) {
|
|
options = ActivityOptions.makeBasic().toBundle();
|
|
}
|
|
result = am.onStartActivity(who, intent, options);
|
|
}
|
|
if (result != null) {
|
|
am.mHits++;
|
|
return result;
|
|
} else if (am.match(who, null, intent)) {
|
|
am.mHits++;
|
|
if (am.isBlocking()) {
|
|
return requestCode >= 0 ? am.getResult() : null;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
intent.migrateExtraStreamToClipData(who);
|
|
intent.prepareToLeaveProcess(who);
|
|
int result = ActivityTaskManager.getService()
|
|
.startActivityAsCaller(whoThread, who.getOpPackageName(), intent,
|
|
intent.resolveTypeIfNeeded(who.getContentResolver()),
|
|
token, target != null ? target.mEmbeddedID : null,
|
|
requestCode, 0, null, options,
|
|
ignoreTargetSecurity, userId);
|
|
notifyStartActivityResult(result, options);
|
|
checkStartActivityResult(result, intent);
|
|
} catch (RemoteException e) {
|
|
throw new RuntimeException("Failure from system", e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Special version!
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public void execStartActivityFromAppTask(
|
|
Context who, IBinder contextThread, IAppTask appTask,
|
|
Intent intent, Bundle options) {
|
|
if (DEBUG_START_ACTIVITY) {
|
|
Log.d(TAG, "startActivity: who=" + who + " intent=" + intent
|
|
+ " options=" + options, new Throwable());
|
|
}
|
|
Objects.requireNonNull(intent);
|
|
IApplicationThread whoThread = (IApplicationThread) contextThread;
|
|
if (isSdkSandboxAllowedToStartActivities()) {
|
|
adjustIntentForCtsInSdkSandboxInstrumentation(intent);
|
|
}
|
|
if (mActivityMonitors != null) {
|
|
synchronized (mSync) {
|
|
final int N = mActivityMonitors.size();
|
|
for (int i=0; i<N; i++) {
|
|
final ActivityMonitor am = mActivityMonitors.get(i);
|
|
ActivityResult result = null;
|
|
if (am.ignoreMatchingSpecificIntents()) {
|
|
if (options == null) {
|
|
options = ActivityOptions.makeBasic().toBundle();
|
|
}
|
|
result = am.onStartActivity(who, intent, options);
|
|
}
|
|
if (result != null) {
|
|
am.mHits++;
|
|
return;
|
|
} else if (am.match(who, null, intent)) {
|
|
am.mHits++;
|
|
if (am.isBlocking()) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
intent.migrateExtraStreamToClipData(who);
|
|
intent.prepareToLeaveProcess(who);
|
|
int result = appTask.startActivity(whoThread.asBinder(), who.getOpPackageName(),
|
|
who.getAttributionTag(), intent,
|
|
intent.resolveTypeIfNeeded(who.getContentResolver()), options);
|
|
notifyStartActivityResult(result, options);
|
|
checkStartActivityResult(result, intent);
|
|
} catch (RemoteException e) {
|
|
throw new RuntimeException("Failure from system", e);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*package*/ final void init(ActivityThread thread,
|
|
Context instrContext, Context appContext, ComponentName component,
|
|
IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection) {
|
|
mThread = thread;
|
|
mMessageQueue = mThread.getLooper().myQueue();
|
|
mInstrContext = instrContext;
|
|
mAppContext = appContext;
|
|
mComponent = component;
|
|
mWatcher = watcher;
|
|
mUiAutomationConnection = uiAutomationConnection;
|
|
}
|
|
|
|
/**
|
|
* Only sets the ActivityThread up, keeps everything else null because app is not being
|
|
* instrumented.
|
|
*/
|
|
final void basicInit(ActivityThread thread) {
|
|
mThread = thread;
|
|
}
|
|
|
|
/**
|
|
* Only sets the Context up, keeps everything else null.
|
|
*
|
|
* @hide
|
|
*/
|
|
@android.ravenwood.annotation.RavenwoodKeep
|
|
public final void basicInit(Context context) {
|
|
mInstrContext = context;
|
|
mAppContext = context;
|
|
}
|
|
|
|
/** @hide */
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
|
public static void checkStartActivityResult(int res, Object intent) {
|
|
if (!ActivityManager.isStartResultFatalError(res)) {
|
|
return;
|
|
}
|
|
|
|
switch (res) {
|
|
case ActivityManager.START_INTENT_NOT_RESOLVED:
|
|
case ActivityManager.START_CLASS_NOT_FOUND:
|
|
if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
|
|
throw new ActivityNotFoundException(
|
|
"Unable to find explicit activity class "
|
|
+ ((Intent)intent).getComponent().toShortString()
|
|
+ "; have you declared this activity in your AndroidManifest.xml"
|
|
+ ", or does your intent not match its declared <intent-filter>?");
|
|
throw new ActivityNotFoundException(
|
|
"No Activity found to handle " + intent);
|
|
case ActivityManager.START_PERMISSION_DENIED:
|
|
throw new SecurityException("Not allowed to start activity "
|
|
+ intent);
|
|
case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
|
|
throw new AndroidRuntimeException(
|
|
"FORWARD_RESULT_FLAG used while also requesting a result");
|
|
case ActivityManager.START_NOT_ACTIVITY:
|
|
throw new IllegalArgumentException(
|
|
"PendingIntent is not an activity");
|
|
case ActivityManager.START_NOT_VOICE_COMPATIBLE:
|
|
throw new SecurityException(
|
|
"Starting under voice control not allowed for: " + intent);
|
|
case ActivityManager.START_VOICE_NOT_ACTIVE_SESSION:
|
|
throw new IllegalStateException(
|
|
"Session calling startVoiceActivity does not match active session");
|
|
case ActivityManager.START_VOICE_HIDDEN_SESSION:
|
|
throw new IllegalStateException(
|
|
"Cannot start voice activity on a hidden session");
|
|
case ActivityManager.START_ASSISTANT_NOT_ACTIVE_SESSION:
|
|
throw new IllegalStateException(
|
|
"Session calling startAssistantActivity does not match active session");
|
|
case ActivityManager.START_ASSISTANT_HIDDEN_SESSION:
|
|
throw new IllegalStateException(
|
|
"Cannot start assistant activity on a hidden session");
|
|
case ActivityManager.START_CANCELED:
|
|
throw new AndroidRuntimeException("Activity could not be started for "
|
|
+ intent);
|
|
default:
|
|
throw new AndroidRuntimeException("Unknown error code "
|
|
+ res + " when starting " + intent);
|
|
}
|
|
}
|
|
|
|
private final void validateNotAppThread() {
|
|
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
throw new RuntimeException(
|
|
"This method can not be called from the main application thread");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the {@link UiAutomation} instance with no flags set.
|
|
* <p>
|
|
* <strong>Note:</strong> The APIs exposed via the returned {@link UiAutomation}
|
|
* work across application boundaries while the APIs exposed by the instrumentation
|
|
* do not. For example, {@link Instrumentation#sendPointerSync(MotionEvent)} will
|
|
* not allow you to inject the event in an app different from the instrumentation
|
|
* target, while {@link UiAutomation#injectInputEvent(android.view.InputEvent, boolean)}
|
|
* will work regardless of the current application.
|
|
* </p>
|
|
* <p>
|
|
* A typical test case should be using either the {@link UiAutomation} or
|
|
* {@link Instrumentation} APIs. Using both APIs at the same time is not
|
|
* a mistake by itself but a client has to be aware of the APIs limitations.
|
|
* </p>
|
|
* <p>
|
|
* Equivalent to {@code getUiAutomation(0)}. If a {@link UiAutomation} exists with different
|
|
* flags, the flags on that instance will be changed, and then it will be returned.
|
|
* </p>
|
|
* <p>
|
|
* Compatibility mode: This method is infallible for apps targeted for
|
|
* {@link Build.VERSION_CODES#R} and earlier versions; for apps targeted for later versions, it
|
|
* will return null if {@link UiAutomation} fails to connect. The caller can check the return
|
|
* value and retry on error.
|
|
* </p>
|
|
*
|
|
* @return The UI automation instance.
|
|
*
|
|
* @see UiAutomation
|
|
*/
|
|
public UiAutomation getUiAutomation() {
|
|
return getUiAutomation(0);
|
|
}
|
|
|
|
/**
|
|
* Gets the {@link UiAutomation} instance with flags set.
|
|
* <p>
|
|
* <strong>Note:</strong> The APIs exposed via the returned {@link UiAutomation}
|
|
* work across application boundaries while the APIs exposed by the instrumentation
|
|
* do not. For example, {@link Instrumentation#sendPointerSync(MotionEvent)} will
|
|
* not allow you to inject the event in an app different from the instrumentation
|
|
* target, while {@link UiAutomation#injectInputEvent(android.view.InputEvent, boolean)}
|
|
* will work regardless of the current application.
|
|
* </p>
|
|
* <p>
|
|
* A typical test case should be using either the {@link UiAutomation} or
|
|
* {@link Instrumentation} APIs. Using both APIs at the same time is not
|
|
* a mistake by itself but a client has to be aware of the APIs limitations.
|
|
* </p>
|
|
* <p>
|
|
* If a {@link UiAutomation} exists with different flags, the flags on that instance will be
|
|
* changed, and then it will be returned.
|
|
* </p>
|
|
* <p>
|
|
* Compatibility mode: This method is infallible for apps targeted for
|
|
* {@link Build.VERSION_CODES#R} and earlier versions; for apps targeted for later versions, it
|
|
* will return null if {@link UiAutomation} fails to connect. The caller can check the return
|
|
* value and retry on error.
|
|
* </p>
|
|
*
|
|
* @param flags The flags to be passed to the UiAutomation, for example
|
|
* {@link UiAutomation#FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES},
|
|
* {@link UiAutomation#FLAG_DONT_USE_ACCESSIBILITY}.
|
|
*
|
|
* @return The UI automation instance.
|
|
*
|
|
* @see UiAutomation
|
|
*/
|
|
public UiAutomation getUiAutomation(@UiAutomationFlags int flags) {
|
|
boolean mustCreateNewAutomation = (mUiAutomation == null) || (mUiAutomation.isDestroyed());
|
|
|
|
if (mUiAutomationConnection != null) {
|
|
if (!mustCreateNewAutomation && (mUiAutomation.getFlags() == flags)) {
|
|
return mUiAutomation;
|
|
}
|
|
if (mustCreateNewAutomation) {
|
|
mUiAutomation = new UiAutomation(getTargetContext(), mUiAutomationConnection);
|
|
} else {
|
|
mUiAutomation.disconnect();
|
|
}
|
|
if (getTargetContext().getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R) {
|
|
mUiAutomation.connect(flags);
|
|
return mUiAutomation;
|
|
}
|
|
final long startUptime = SystemClock.uptimeMillis();
|
|
try {
|
|
mUiAutomation.connectWithTimeout(flags, CONNECT_TIMEOUT_MILLIS);
|
|
return mUiAutomation;
|
|
} catch (TimeoutException e) {
|
|
final long waited = SystemClock.uptimeMillis() - startUptime;
|
|
Log.e(TAG, "Unable to connect to UiAutomation. Waited for " + waited + " ms", e);
|
|
mUiAutomation.destroy();
|
|
mUiAutomation = null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Takes control of the execution of messages on the specified looper until
|
|
* {@link TestLooperManager#release} is called.
|
|
*/
|
|
@android.ravenwood.annotation.RavenwoodKeep
|
|
public TestLooperManager acquireLooperManager(Looper looper) {
|
|
checkInstrumenting("acquireLooperManager");
|
|
return new TestLooperManager(looper);
|
|
}
|
|
|
|
private final class InstrumentationThread extends Thread {
|
|
public InstrumentationThread(String name) {
|
|
super(name);
|
|
}
|
|
public void run() {
|
|
try {
|
|
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
|
|
} catch (RuntimeException e) {
|
|
Log.w(TAG, "Exception setting priority of instrumentation thread "
|
|
+ Process.myTid(), e);
|
|
}
|
|
if (mAutomaticPerformanceSnapshots) {
|
|
startPerformanceSnapshot();
|
|
}
|
|
onStart();
|
|
}
|
|
}
|
|
|
|
private static final class EmptyRunnable implements Runnable {
|
|
public void run() {
|
|
}
|
|
}
|
|
|
|
private static final class SyncRunnable implements Runnable {
|
|
private final Runnable mTarget;
|
|
private boolean mComplete;
|
|
|
|
public SyncRunnable(Runnable target) {
|
|
mTarget = target;
|
|
}
|
|
|
|
public void run() {
|
|
mTarget.run();
|
|
synchronized (this) {
|
|
mComplete = true;
|
|
notifyAll();
|
|
}
|
|
}
|
|
|
|
public void waitForComplete() {
|
|
synchronized (this) {
|
|
while (!mComplete) {
|
|
try {
|
|
wait();
|
|
} catch (InterruptedException e) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final class ActivityWaiter {
|
|
public final Intent intent;
|
|
public Activity activity;
|
|
|
|
public ActivityWaiter(Intent _intent) {
|
|
intent = _intent;
|
|
}
|
|
}
|
|
|
|
private final class ActivityGoing implements MessageQueue.IdleHandler {
|
|
private final ActivityWaiter mWaiter;
|
|
|
|
public ActivityGoing(ActivityWaiter waiter) {
|
|
mWaiter = waiter;
|
|
}
|
|
|
|
public final boolean queueIdle() {
|
|
synchronized (mSync) {
|
|
mWaitingActivities.remove(mWaiter);
|
|
mSync.notifyAll();
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static final class Idler implements MessageQueue.IdleHandler {
|
|
private final Runnable mCallback;
|
|
private boolean mIdle;
|
|
|
|
public Idler(Runnable callback) {
|
|
mCallback = callback;
|
|
mIdle = false;
|
|
}
|
|
|
|
public final boolean queueIdle() {
|
|
if (mCallback != null) {
|
|
mCallback.run();
|
|
}
|
|
synchronized (this) {
|
|
mIdle = true;
|
|
notifyAll();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void waitForIdle() {
|
|
synchronized (this) {
|
|
while (!mIdle) {
|
|
try {
|
|
wait();
|
|
} catch (InterruptedException e) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|