1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package android.server.am; 18 19 import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; 20 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; 21 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 22 import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT; 23 import static android.server.am.Components.TEST_ACTIVITY; 24 25 import android.app.ActivityOptions; 26 import android.app.PendingIntent; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.text.TextUtils; 33 import android.util.Log; 34 35 /** Utility class which contains common code for launching activities. */ 36 public class ActivityLauncher { 37 private static final String TAG = ActivityLauncher.class.getSimpleName(); 38 39 /** Key for boolean extra, indicates whether it should launch an activity. */ 40 public static final String KEY_LAUNCH_ACTIVITY = "launch_activity"; 41 /** 42 * Key for boolean extra, indicates whether it the activity should be launched to side in 43 * split-screen. 44 */ 45 public static final String KEY_LAUNCH_TO_SIDE = "launch_to_the_side"; 46 /** 47 * Key for boolean extra, indicates if launch intent should include random data to be different 48 * from other launch intents. 49 */ 50 public static final String KEY_RANDOM_DATA = "random_data"; 51 /** 52 * Key for boolean extra, indicates if launch intent should have 53 * {@link Intent#FLAG_ACTIVITY_NEW_TASK}. 54 */ 55 public static final String KEY_NEW_TASK = "new_task"; 56 /** 57 * Key for boolean extra, indicates if launch intent should have 58 * {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}. 59 */ 60 public static final String KEY_MULTIPLE_TASK = "multiple_task"; 61 /** 62 * Key for boolean extra, indicates if launch intent should have 63 * {@link Intent#FLAG_ACTIVITY_REORDER_TO_FRONT}. 64 */ 65 public static final String KEY_REORDER_TO_FRONT = "reorder_to_front"; 66 /** 67 * Key for string extra with string representation of target component. 68 */ 69 public static final String KEY_TARGET_COMPONENT = "target_component"; 70 /** 71 * Key for int extra with target display id where the activity should be launched. Adding this 72 * automatically applies {@link Intent#FLAG_ACTIVITY_NEW_TASK} and 73 * {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to the intent. 74 */ 75 public static final String KEY_DISPLAY_ID = "display_id"; 76 /** 77 * Key for boolean extra, indicates if launch should be done from application context of the one 78 * passed in {@link #launchActivityFromExtras(Context, Bundle)}. 79 */ 80 public static final String KEY_USE_APPLICATION_CONTEXT = "use_application_context"; 81 /** 82 * Key for boolean extra, indicates if instrumentation context will be used for launch. This 83 * means that {@link PendingIntent} should be used instead of a regular one, because application 84 * switch will not be allowed otherwise. 85 */ 86 public static final String KEY_USE_INSTRUMENTATION = "use_instrumentation"; 87 /** 88 * Key for boolean extra, indicates if any exceptions thrown during launch other then 89 * {@link SecurityException} should be suppressed. A {@link SecurityException} is never thrown, 90 * it's always written to logs. 91 */ 92 public static final String KEY_SUPPRESS_EXCEPTIONS = "suppress_exceptions"; 93 94 95 /** Perform an activity launch configured by provided extras. */ launchActivityFromExtras(final Context context, Bundle extras)96 public static void launchActivityFromExtras(final Context context, Bundle extras) { 97 if (extras == null || !extras.getBoolean(KEY_LAUNCH_ACTIVITY)) { 98 return; 99 } 100 101 Log.i(TAG, "launchActivityFromExtras: extras=" + extras); 102 103 final String targetComponent = extras.getString(KEY_TARGET_COMPONENT); 104 final Intent newIntent = new Intent().setComponent(TextUtils.isEmpty(targetComponent) 105 ? TEST_ACTIVITY : ComponentName.unflattenFromString(targetComponent)); 106 107 if (extras.getBoolean(KEY_LAUNCH_TO_SIDE)) { 108 newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT); 109 if (extras.getBoolean(KEY_RANDOM_DATA)) { 110 final Uri data = new Uri.Builder() 111 .path(String.valueOf(System.currentTimeMillis())) 112 .build(); 113 newIntent.setData(data); 114 } 115 } 116 if (extras.getBoolean(KEY_MULTIPLE_TASK)) { 117 newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); 118 } 119 if (extras.getBoolean(KEY_NEW_TASK)) { 120 newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK); 121 } 122 123 if (extras.getBoolean(KEY_REORDER_TO_FRONT)) { 124 newIntent.addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT); 125 } 126 127 ActivityOptions options = null; 128 final int displayId = extras.getInt(KEY_DISPLAY_ID, -1); 129 if (displayId != -1) { 130 options = ActivityOptions.makeBasic(); 131 options.setLaunchDisplayId(displayId); 132 newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK); 133 } 134 final Bundle optionsBundle = options != null ? options.toBundle() : null; 135 136 final Context launchContext = extras.getBoolean(KEY_USE_APPLICATION_CONTEXT) ? 137 context.getApplicationContext() : context; 138 139 try { 140 if (extras.getBoolean(KEY_USE_INSTRUMENTATION)) { 141 // Using PendingIntent for Instrumentation launches, because otherwise we won't 142 // be allowed to switch the current activity with ours with different uid. 143 // android.permission.STOP_APP_SWITCHES is needed to do this directly. 144 final PendingIntent pendingIntent = PendingIntent.getActivity(launchContext, 0, 145 newIntent, 0, optionsBundle); 146 pendingIntent.send(); 147 } else { 148 launchContext.startActivity(newIntent, optionsBundle); 149 } 150 } catch (SecurityException e) { 151 Log.e(TAG, "SecurityException launching activity"); 152 } catch (PendingIntent.CanceledException e) { 153 if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) { 154 Log.e(TAG, "Exception launching activity with pending intent"); 155 } else { 156 throw new RuntimeException(e); 157 } 158 Log.e(TAG, "SecurityException launching activity"); 159 } catch (Exception e) { 160 if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) { 161 Log.e(TAG, "Exception launching activity"); 162 } else { 163 throw e; 164 } 165 } 166 } 167 } 168