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.wm; 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.wm.app.Components.TEST_ACTIVITY; 24 25 import android.app.ActivityManager; 26 import android.app.ActivityOptions; 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.server.wm.CommandSession.LaunchInjector; 33 import android.server.wm.TestJournalProvider.TestJournalContainer; 34 import android.text.TextUtils; 35 import android.util.Log; 36 37 /** Utility class which contains common code for launching activities. */ 38 public class ActivityLauncher { 39 public static final String TAG = ActivityLauncher.class.getSimpleName(); 40 41 /** Key for boolean extra, indicates whether it should launch an activity. */ 42 public static final String KEY_LAUNCH_ACTIVITY = "launch_activity"; 43 /** 44 * Key for boolean extra, indicates whether it the activity should be launched to side in 45 * split-screen. 46 */ 47 public static final String KEY_LAUNCH_TO_SIDE = "launch_to_the_side"; 48 /** 49 * Key for boolean extra, indicates if launch intent should include random data to be different 50 * from other launch intents. 51 */ 52 public static final String KEY_RANDOM_DATA = "random_data"; 53 /** 54 * Key for boolean extra, indicates if launch intent should have 55 * {@link Intent#FLAG_ACTIVITY_NEW_TASK}. 56 */ 57 public static final String KEY_NEW_TASK = "new_task"; 58 /** 59 * Key for boolean extra, indicates if launch intent should have 60 * {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}. 61 */ 62 public static final String KEY_MULTIPLE_TASK = "multiple_task"; 63 /** 64 * Key for boolean extra, indicates if launch intent should have 65 * {@link Intent#FLAG_ACTIVITY_REORDER_TO_FRONT}. 66 */ 67 public static final String KEY_REORDER_TO_FRONT = "reorder_to_front"; 68 /** 69 * Key for boolean extra, indicates if launch task without presented to user. 70 * {@link ActivityOptions#makeTaskLaunchBehind()}. 71 */ 72 public static final String KEY_LAUNCH_TASK_BEHIND = "launch_task_behind"; 73 /** 74 * Key for string extra with string representation of target component. 75 */ 76 public static final String KEY_TARGET_COMPONENT = "target_component"; 77 /** 78 * Key for int extra with target display id where the activity should be launched. Adding this 79 * automatically applies {@link Intent#FLAG_ACTIVITY_NEW_TASK} and 80 * {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to the intent. 81 */ 82 public static final String KEY_DISPLAY_ID = "display_id"; 83 /** 84 * Key for boolean extra, indicates if launch should be done from application context of the one 85 * passed in {@link #launchActivityFromExtras(Context, Bundle)}. 86 */ 87 public static final String KEY_USE_APPLICATION_CONTEXT = "use_application_context"; 88 /** 89 * Key for boolean extra, indicates if any exceptions thrown during launch other then 90 * {@link SecurityException} should be suppressed. A {@link SecurityException} is never thrown, 91 * it's always written to logs. 92 */ 93 public static final String KEY_SUPPRESS_EXCEPTIONS = "suppress_exceptions"; 94 /** 95 * Key for boolean extra, indicates the result of 96 * {@link ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)} 97 */ 98 public static final String KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY = 99 "is_activity_start_allowed_on_display"; 100 /** 101 * Key for boolean extra, indicates a security exception is caught when launching activity by 102 * {@link #launchActivityFromExtras}. 103 */ 104 private static final String KEY_CAUGHT_SECURITY_EXCEPTION = "caught_security_exception"; 105 /** 106 * Key for int extra with target activity type where activity should be launched as. 107 */ 108 public static final String KEY_ACTIVITY_TYPE = "activity_type"; 109 /** 110 * Key for int extra with intent flags which are used for launching an activity. 111 */ 112 public static final String KEY_INTENT_FLAGS = "intent_flags"; 113 /** 114 * Key for boolean extra, indicates if need to automatically applies 115 * {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to 116 * the intent when target display id set. 117 */ 118 public static final String KEY_MULTIPLE_INSTANCES = "multiple_instances"; 119 120 /** 121 * Key for bundle extra to the intent which are used for launching an activity. 122 */ 123 public static final String KEY_INTENT_EXTRAS = "intent_extras"; 124 125 /** 126 * Key for int extra, indicates the requested windowing mode. 127 */ 128 public static final String KEY_WINDOWING_MODE = "windowing_mode"; 129 130 131 /** Perform an activity launch configured by provided extras. */ launchActivityFromExtras(final Context context, Bundle extras)132 public static void launchActivityFromExtras(final Context context, Bundle extras) { 133 launchActivityFromExtras(context, extras, null /* launchInjector */); 134 } 135 launchActivityFromExtras(final Context context, Bundle extras, LaunchInjector launchInjector)136 public static void launchActivityFromExtras(final Context context, Bundle extras, 137 LaunchInjector launchInjector) { 138 if (extras == null || !extras.getBoolean(KEY_LAUNCH_ACTIVITY)) { 139 return; 140 } 141 142 Log.i(TAG, "launchActivityFromExtras: extras=" + extras); 143 144 final String targetComponent = extras.getString(KEY_TARGET_COMPONENT); 145 final Intent newIntent = new Intent().setComponent(TextUtils.isEmpty(targetComponent) 146 ? TEST_ACTIVITY : ComponentName.unflattenFromString(targetComponent)); 147 148 if (extras.getBoolean(KEY_LAUNCH_TO_SIDE)) { 149 newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT); 150 if (extras.getBoolean(KEY_RANDOM_DATA)) { 151 final Uri data = new Uri.Builder() 152 .path(String.valueOf(System.currentTimeMillis())) 153 .build(); 154 newIntent.setData(data); 155 } 156 } 157 if (extras.getBoolean(KEY_MULTIPLE_TASK)) { 158 newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); 159 } 160 if (extras.getBoolean(KEY_NEW_TASK)) { 161 newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK); 162 } 163 164 if (extras.getBoolean(KEY_REORDER_TO_FRONT)) { 165 newIntent.addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT); 166 } 167 168 final Bundle intentExtras = extras.getBundle(KEY_INTENT_EXTRAS) ; 169 if (intentExtras != null) { 170 newIntent.putExtras(intentExtras); 171 } 172 173 ActivityOptions options = extras.getBoolean(KEY_LAUNCH_TASK_BEHIND) 174 ? ActivityOptions.makeTaskLaunchBehind() : null; 175 final int displayId = extras.getInt(KEY_DISPLAY_ID, -1); 176 if (displayId != -1) { 177 if (options == null) { 178 options = ActivityOptions.makeBasic(); 179 } 180 options.setLaunchDisplayId(displayId); 181 if (extras.getBoolean(KEY_MULTIPLE_INSTANCES)) { 182 newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK); 183 } 184 } 185 final int windowingMode = extras.getInt(KEY_WINDOWING_MODE, -1); 186 if (windowingMode != -1) { 187 if (options == null) { 188 options = ActivityOptions.makeBasic(); 189 } 190 options.setLaunchWindowingMode(windowingMode); 191 } 192 if (launchInjector != null) { 193 launchInjector.setupIntent(newIntent); 194 } 195 final int activityType = extras.getInt(KEY_ACTIVITY_TYPE, -1); 196 if (activityType != -1) { 197 if (options == null) { 198 options = ActivityOptions.makeBasic(); 199 } 200 options.setLaunchActivityType(activityType); 201 } 202 final int intentFlags = extras.getInt(KEY_INTENT_FLAGS); // 0 if key doesn't exist. 203 if (intentFlags != 0) { 204 newIntent.addFlags(intentFlags); 205 } 206 final Bundle optionsBundle = options != null ? options.toBundle() : null; 207 208 final Context launchContext = extras.getBoolean(KEY_USE_APPLICATION_CONTEXT) ? 209 context.getApplicationContext() : context; 210 211 try { 212 launchContext.startActivity(newIntent, optionsBundle); 213 } catch (SecurityException e) { 214 handleSecurityException(context, e); 215 } catch (Exception e) { 216 if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) { 217 Log.e(TAG, "Exception launching activity"); 218 } else { 219 throw e; 220 } 221 } 222 } 223 checkActivityStartOnDisplay(Context context, int displayId, ComponentName componentName)224 public static void checkActivityStartOnDisplay(Context context, int displayId, 225 ComponentName componentName) { 226 final Intent launchIntent = new Intent(Intent.ACTION_VIEW).setComponent(componentName); 227 228 final boolean isAllowed = context.getSystemService(ActivityManager.class) 229 .isActivityStartAllowedOnDisplay(context, displayId, launchIntent); 230 Log.i(TAG, "isActivityStartAllowedOnDisplay=" + isAllowed); 231 TestJournalProvider.putExtras(context, TAG, bundle -> { 232 bundle.putBoolean(KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY, isAllowed); 233 }); 234 } 235 handleSecurityException(Context context, Exception e)236 public static void handleSecurityException(Context context, Exception e) { 237 Log.e(TAG, "SecurityException launching activity: " + e); 238 TestJournalProvider.putExtras(context, TAG, bundle -> { 239 bundle.putBoolean(KEY_CAUGHT_SECURITY_EXCEPTION, true); 240 }); 241 } 242 hasCaughtSecurityException()243 static boolean hasCaughtSecurityException() { 244 return TestJournalContainer.get(TAG).extras.containsKey(KEY_CAUGHT_SECURITY_EXCEPTION); 245 } 246 } 247