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