• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.am;
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.am.Components.TEST_ACTIVITY;
24  
25  import android.app.ActivityOptions;
26  import android.app.PendingIntent;
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.text.TextUtils;
33  import android.util.Log;
34  
35  /** Utility class which contains common code for launching activities. */
36  public class ActivityLauncher {
37      private static final String TAG = ActivityLauncher.class.getSimpleName();
38  
39      /** Key for boolean extra, indicates whether it should launch an activity. */
40      public static final String KEY_LAUNCH_ACTIVITY = "launch_activity";
41      /**
42       * Key for boolean extra, indicates whether it the activity should be launched to side in
43       * split-screen.
44       */
45      public static final String KEY_LAUNCH_TO_SIDE = "launch_to_the_side";
46      /**
47       * Key for boolean extra, indicates if launch intent should include random data to be different
48       * from other launch intents.
49       */
50      public static final String KEY_RANDOM_DATA = "random_data";
51      /**
52       * Key for boolean extra, indicates if launch intent should have
53       * {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
54       */
55      public static final String KEY_NEW_TASK = "new_task";
56      /**
57       * Key for boolean extra, indicates if launch intent should have
58       * {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}.
59       */
60      public static final String KEY_MULTIPLE_TASK = "multiple_task";
61      /**
62       * Key for boolean extra, indicates if launch intent should have
63       * {@link Intent#FLAG_ACTIVITY_REORDER_TO_FRONT}.
64       */
65      public static final String KEY_REORDER_TO_FRONT = "reorder_to_front";
66      /**
67       * Key for string extra with string representation of target component.
68       */
69      public static final String KEY_TARGET_COMPONENT = "target_component";
70      /**
71       * Key for int extra with target display id where the activity should be launched. Adding this
72       * automatically applies {@link Intent#FLAG_ACTIVITY_NEW_TASK} and
73       * {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to the intent.
74       */
75      public static final String KEY_DISPLAY_ID = "display_id";
76      /**
77       * Key for boolean extra, indicates if launch should be done from application context of the one
78       * passed in {@link #launchActivityFromExtras(Context, Bundle)}.
79       */
80      public static final String KEY_USE_APPLICATION_CONTEXT = "use_application_context";
81      /**
82       * Key for boolean extra, indicates if instrumentation context will be used for launch. This
83       * means that {@link PendingIntent} should be used instead of a regular one, because application
84       * switch will not be allowed otherwise.
85       */
86      public static final String KEY_USE_INSTRUMENTATION = "use_instrumentation";
87      /**
88       * Key for boolean extra, indicates if any exceptions thrown during launch other then
89       * {@link SecurityException} should be suppressed. A {@link SecurityException} is never thrown,
90       * it's always written to logs.
91       */
92      public static final String KEY_SUPPRESS_EXCEPTIONS = "suppress_exceptions";
93  
94  
95      /** Perform an activity launch configured by provided extras. */
launchActivityFromExtras(final Context context, Bundle extras)96      public static void launchActivityFromExtras(final Context context, Bundle extras) {
97          if (extras == null || !extras.getBoolean(KEY_LAUNCH_ACTIVITY)) {
98              return;
99          }
100  
101          Log.i(TAG, "launchActivityFromExtras: extras=" + extras);
102  
103          final String targetComponent = extras.getString(KEY_TARGET_COMPONENT);
104          final Intent newIntent = new Intent().setComponent(TextUtils.isEmpty(targetComponent)
105                  ? TEST_ACTIVITY : ComponentName.unflattenFromString(targetComponent));
106  
107          if (extras.getBoolean(KEY_LAUNCH_TO_SIDE)) {
108              newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT);
109              if (extras.getBoolean(KEY_RANDOM_DATA)) {
110                  final Uri data = new Uri.Builder()
111                          .path(String.valueOf(System.currentTimeMillis()))
112                          .build();
113                  newIntent.setData(data);
114              }
115          }
116          if (extras.getBoolean(KEY_MULTIPLE_TASK)) {
117              newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
118          }
119          if (extras.getBoolean(KEY_NEW_TASK)) {
120              newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
121          }
122  
123          if (extras.getBoolean(KEY_REORDER_TO_FRONT)) {
124              newIntent.addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);
125          }
126  
127          ActivityOptions options = null;
128          final int displayId = extras.getInt(KEY_DISPLAY_ID, -1);
129          if (displayId != -1) {
130              options = ActivityOptions.makeBasic();
131              options.setLaunchDisplayId(displayId);
132              newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
133          }
134          final Bundle optionsBundle = options != null ? options.toBundle() : null;
135  
136          final Context launchContext = extras.getBoolean(KEY_USE_APPLICATION_CONTEXT) ?
137                  context.getApplicationContext() : context;
138  
139          try {
140              if (extras.getBoolean(KEY_USE_INSTRUMENTATION)) {
141                  // Using PendingIntent for Instrumentation launches, because otherwise we won't
142                  // be allowed to switch the current activity with ours with different uid.
143                  // android.permission.STOP_APP_SWITCHES is needed to do this directly.
144                  final PendingIntent pendingIntent = PendingIntent.getActivity(launchContext, 0,
145                          newIntent, 0, optionsBundle);
146                  pendingIntent.send();
147              } else {
148                  launchContext.startActivity(newIntent, optionsBundle);
149              }
150          } catch (SecurityException e) {
151              Log.e(TAG, "SecurityException launching activity");
152          } catch (PendingIntent.CanceledException e) {
153              if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) {
154                  Log.e(TAG, "Exception launching activity with pending intent");
155              } else {
156                  throw new RuntimeException(e);
157              }
158              Log.e(TAG, "SecurityException launching activity");
159          } catch (Exception e) {
160              if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) {
161                  Log.e(TAG, "Exception launching activity");
162              } else {
163                  throw e;
164              }
165          }
166      }
167  }
168