blob: 63ec592f281a3fa5fedb9a252b6f973b1d4aeab6 [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.am;
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.am.Components.TEST_ACTIVITY;
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.am.CommandSession.LaunchInjector;
import android.text.TextUtils;
import android.util.Log;
/** Utility class which contains common code for launching activities. */
public class ActivityLauncher {
private static final String TAG = ActivityLauncher.class.getSimpleName();
/** 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 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 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 instrumentation context will be used for launch. This
* means that {@link PendingIntent} should be used instead of a regular one, because application
* switch will not be allowed otherwise.
*/
public static final String KEY_USE_INSTRUMENTATION = "use_instrumentation";
/**
* 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 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";
/** Perform an activity launch configured by provided extras. */
public static void launchActivityFromExtras(final Context context, Bundle extras) {
launchActivityFromExtras(context, extras, null /* launchInjector */);
}
public static void launchActivityFromExtras(final Context context, Bundle extras,
LaunchInjector launchInjector) {
if (extras == null || !extras.getBoolean(KEY_LAUNCH_ACTIVITY)) {
return;
}
Log.i(TAG, "launchActivityFromExtras: extras=" + extras);
final String targetComponent = extras.getString(KEY_TARGET_COMPONENT);
final Intent newIntent = new Intent().setComponent(TextUtils.isEmpty(targetComponent)
? TEST_ACTIVITY : ComponentName.unflattenFromString(targetComponent));
if (extras.getBoolean(KEY_LAUNCH_TO_SIDE)) {
newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT);
if (extras.getBoolean(KEY_RANDOM_DATA)) {
final Uri data = new Uri.Builder()
.path(String.valueOf(System.currentTimeMillis()))
.build();
newIntent.setData(data);
}
}
if (extras.getBoolean(KEY_MULTIPLE_TASK)) {
newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
}
if (extras.getBoolean(KEY_NEW_TASK)) {
newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
}
if (extras.getBoolean(KEY_REORDER_TO_FRONT)) {
newIntent.addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);
}
ActivityOptions options = null;
final int displayId = extras.getInt(KEY_DISPLAY_ID, -1);
if (displayId != -1) {
options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(displayId);
if (extras.getBoolean(KEY_MULTIPLE_INSTANCES, true)) {
newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
}
}
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 = extras.getBoolean(KEY_USE_APPLICATION_CONTEXT) ?
context.getApplicationContext() : context;
try {
if (extras.getBoolean(KEY_USE_INSTRUMENTATION)) {
// Using PendingIntent for Instrumentation launches, because otherwise we won't
// be allowed to switch the current activity with ours with different uid.
// android.permission.STOP_APP_SWITCHES is needed to do this directly.
// PendingIntent.FLAG_CANCEL_CURRENT is needed here, or we may get an existing
// PendingIntent if it is same kind of PendingIntent request to previous one.
// Note: optionsBundle is not taking into account for PendingIntentRecord.Key
// hashcode calculation.
final PendingIntent pendingIntent = PendingIntent.getActivity(launchContext, 0,
newIntent, PendingIntent.FLAG_CANCEL_CURRENT, optionsBundle);
pendingIntent.send();
} else {
launchContext.startActivity(newIntent, optionsBundle);
}
} catch (SecurityException e) {
Log.e(TAG, "SecurityException launching activity");
} catch (PendingIntent.CanceledException e) {
if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) {
Log.e(TAG, "Exception launching activity with pending intent");
} else {
throw new RuntimeException(e);
}
Log.e(TAG, "SecurityException launching activity");
} catch (Exception e) {
if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) {
Log.e(TAG, "Exception launching activity");
} else {
throw e;
}
}
}
}