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