blob: 25b5ee8560fc0e17ba790123ffda77165ec720a9 [file] [log] [blame]
/*
* Copyright (C) 2018 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.server.wm;
import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
import static android.server.wm.app.Components.TEST_ACTIVITY;
import static android.server.wm.second.Components.IMPLICIT_TARGET_SECOND_TEST_ACTION;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.server.wm.CommandSession.LaunchInjector;
import android.server.wm.TestJournalProvider.TestJournalContainer;
import android.text.TextUtils;
import android.util.Log;
/** Utility class which contains common code for launching activities. */
public class ActivityLauncher {
public static final String TAG = ActivityLauncher.class.getSimpleName();
/** Key for string extra, indicates the action to apply. */
public static final String KEY_ACTION = "intent_action";
/** Key for boolean extra, indicates whether it should launch an activity. */
public static final String KEY_LAUNCH_ACTIVITY = "launch_activity";
/** Key for boolean extra, indicates whether it should launch implicitly. */
public static final String KEY_LAUNCH_IMPLICIT = "launch_implicit";
/** Key for boolean extra, indicates whether it should launch fromm pending intent. */
public static final String KEY_LAUNCH_PENDING = "launch_pending";
/**
* Key for boolean extra, indicates whether it the activity should be launched to side in
* split-screen.
*/
public static final String KEY_LAUNCH_TO_SIDE = "launch_to_the_side";
/**
* Key for boolean extra, indicates if launch intent should include random data to be different
* from other launch intents.
*/
public static final String KEY_RANDOM_DATA = "random_data";
/**
* Key for boolean extra, indicates if launch intent should have
* {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
*/
public static final String KEY_NEW_TASK = "new_task";
/**
* Key for boolean extra, indicates if launch intent should have
* {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}.
*/
public static final String KEY_MULTIPLE_TASK = "multiple_task";
/**
* Key for boolean extra, indicates if launch intent should have
* {@link Intent#FLAG_ACTIVITY_REORDER_TO_FRONT}.
*/
public static final String KEY_REORDER_TO_FRONT = "reorder_to_front";
/**
* Key for boolean extra, indicates if launch task without presented to user.
* {@link ActivityOptions#makeTaskLaunchBehind()}.
*/
public static final String KEY_LAUNCH_TASK_BEHIND = "launch_task_behind";
/**
* Key for string extra with string representation of target component.
*/
public static final String KEY_TARGET_COMPONENT = "target_component";
/**
* Key for int extra with target display id where the activity should be launched. Adding this
* automatically applies {@link Intent#FLAG_ACTIVITY_NEW_TASK} and
* {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to the intent.
*/
public static final String KEY_DISPLAY_ID = "display_id";
/**
* Key for boolean extra, indicates if launch should be done from application context of the one
* passed in {@link #launchActivityFromExtras(Context, Bundle)}.
*/
public static final String KEY_USE_APPLICATION_CONTEXT = "use_application_context";
/**
* Key for boolean extra, indicates if any exceptions thrown during launch other then
* {@link SecurityException} should be suppressed. A {@link SecurityException} is never thrown,
* it's always written to logs.
*/
public static final String KEY_SUPPRESS_EXCEPTIONS = "suppress_exceptions";
/**
* Key for boolean extra, indicates the result of
* {@link ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
*/
public static final String KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY =
"is_activity_start_allowed_on_display";
/**
* Key for boolean extra, indicates a security exception is caught when launching activity by
* {@link #launchActivityFromExtras}.
*/
private static final String KEY_CAUGHT_SECURITY_EXCEPTION = "caught_security_exception";
/**
* Key for boolean extra, indicates a pending intent canceled exception is caught when
* launching activity by {@link #launchActivityFromExtras}.
*/
private static final String KEY_CAUGHT_PENDING_INTENT_CANCELED_EXCEPTION =
"caught_pending_intent_exception";
/**
* Key for int extra with target activity type where activity should be launched as.
*/
public static final String KEY_ACTIVITY_TYPE = "activity_type";
/**
* Key for int extra with intent flags which are used for launching an activity.
*/
public static final String KEY_INTENT_FLAGS = "intent_flags";
/**
* Key for boolean extra, indicates if need to automatically applies
* {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to
* the intent when target display id set.
*/
public static final String KEY_MULTIPLE_INSTANCES = "multiple_instances";
/**
* Key for bundle extra to the intent which are used for launching an activity.
*/
public static final String KEY_INTENT_EXTRAS = "intent_extras";
/**
* Key for int extra, indicates the requested windowing mode.
*/
public static final String KEY_WINDOWING_MODE = "windowing_mode";
/** Perform an activity launch configured by provided extras. */
public static void launchActivityFromExtras(final Context context, Bundle extras) {
launchActivityFromExtras(context, extras, null /* launchInjector */);
}
/**
* A convenience method to default to false if the extras are null.
*
* @param extras {@link Bundle} extras used to launch activity
* @param key key to look up in extras
* @return the value for the given key in the extra or false if extras is null
*/
private static boolean getBoolean(Bundle extras, String key) {
return extras != null && extras.getBoolean(key);
}
public static void launchActivityFromExtras(final Context context, Bundle extras,
LaunchInjector launchInjector) {
if (!getBoolean(extras, KEY_LAUNCH_ACTIVITY)) {
return;
}
Log.i(TAG, "launchActivityFromExtras: extras=" + extras);
final Intent newIntent = new Intent();
if (getBoolean(extras, KEY_LAUNCH_IMPLICIT)) {
newIntent.setAction(extras.getString(KEY_ACTION, IMPLICIT_TARGET_SECOND_TEST_ACTION));
} else {
final String targetComponent = extras.getString(KEY_TARGET_COMPONENT);
final ComponentName componentName = TextUtils.isEmpty(targetComponent)
? TEST_ACTIVITY : ComponentName.unflattenFromString(targetComponent);
newIntent.setComponent(componentName);
}
if (getBoolean(extras, KEY_LAUNCH_TO_SIDE)) {
newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT);
if (getBoolean(extras, KEY_RANDOM_DATA)) {
final Uri data = new Uri.Builder()
.path(String.valueOf(System.currentTimeMillis()))
.build();
newIntent.setData(data);
}
}
if (getBoolean(extras, KEY_MULTIPLE_TASK)) {
newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
}
if (getBoolean(extras, KEY_NEW_TASK)) {
newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
}
if (getBoolean(extras, KEY_REORDER_TO_FRONT)) {
newIntent.addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);
}
final Bundle intentExtras = extras.getBundle(KEY_INTENT_EXTRAS) ;
if (intentExtras != null) {
newIntent.putExtras(intentExtras);
}
ActivityOptions options = extras.getBoolean(KEY_LAUNCH_TASK_BEHIND)
? ActivityOptions.makeTaskLaunchBehind() : null;
final int displayId = extras.getInt(KEY_DISPLAY_ID, -1);
if (displayId != -1) {
if (options == null) {
options = ActivityOptions.makeBasic();
}
options.setLaunchDisplayId(displayId);
if (extras.getBoolean(KEY_MULTIPLE_INSTANCES)) {
newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
}
}
final int windowingMode = extras.getInt(KEY_WINDOWING_MODE, -1);
if (windowingMode != -1) {
if (options == null) {
options = ActivityOptions.makeBasic();
}
options.setLaunchWindowingMode(windowingMode);
}
if (launchInjector != null) {
launchInjector.setupIntent(newIntent);
}
final int activityType = extras.getInt(KEY_ACTIVITY_TYPE, -1);
if (activityType != -1) {
if (options == null) {
options = ActivityOptions.makeBasic();
}
options.setLaunchActivityType(activityType);
}
final int intentFlags = extras.getInt(KEY_INTENT_FLAGS); // 0 if key doesn't exist.
if (intentFlags != 0) {
newIntent.addFlags(intentFlags);
}
final Bundle optionsBundle = options != null ? options.toBundle() : null;
final Context launchContext = getBoolean(extras, KEY_USE_APPLICATION_CONTEXT) ?
context.getApplicationContext() : context;
try {
if (getBoolean(extras, KEY_LAUNCH_PENDING)) {
PendingIntent pendingIntent = PendingIntent.getActivity(launchContext,
0, newIntent, PendingIntent.FLAG_IMMUTABLE);
pendingIntent.send();
} else {
launchContext.startActivity(newIntent, optionsBundle);
}
} catch (SecurityException e) {
handleSecurityException(context, e);
} catch (PendingIntent.CanceledException e) {
handlePendingIntentCanceled(context, e);
} catch (Exception e) {
if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) {
Log.e(TAG, "Exception launching activity");
} else {
throw e;
}
}
}
public static void checkActivityStartOnDisplay(Context context, int displayId,
ComponentName componentName) {
final Intent launchIntent = new Intent(Intent.ACTION_VIEW).setComponent(componentName);
final boolean isAllowed = context.getSystemService(ActivityManager.class)
.isActivityStartAllowedOnDisplay(context, displayId, launchIntent);
Log.i(TAG, "isActivityStartAllowedOnDisplay=" + isAllowed);
TestJournalProvider.putExtras(context, TAG, bundle -> {
bundle.putBoolean(KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY, isAllowed);
});
}
public static void handleSecurityException(Context context, Exception e) {
Log.e(TAG, "SecurityException launching activity: " + e);
TestJournalProvider.putExtras(context, TAG, bundle -> {
bundle.putBoolean(KEY_CAUGHT_SECURITY_EXCEPTION, true);
});
}
public static void handlePendingIntentCanceled(Context context, Exception e) {
Log.e(TAG, "PendingIntent.CanceledException launching activity: " + e);
TestJournalProvider.putExtras(context, TAG, bundle -> {
bundle.putBoolean(KEY_CAUGHT_PENDING_INTENT_CANCELED_EXCEPTION, true);
});
}
static boolean hasCaughtSecurityException() {
return TestJournalContainer.get(TAG).extras.containsKey(KEY_CAUGHT_SECURITY_EXCEPTION);
}
}