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