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