| /* |
| * 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) { |
| } |
| } |
| } |
| } |
| } |
| } |