Home | History | Annotate | Download | only in wm
      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 
     25 import android.app.ActivityManager;
     26 import android.app.ActivityOptions;
     27 import android.app.PendingIntent;
     28 import android.content.ComponentName;
     29 import android.content.Context;
     30 import android.content.Intent;
     31 import android.net.Uri;
     32 import android.os.Bundle;
     33 import android.server.wm.CommandSession.LaunchInjector;
     34 import android.server.wm.TestJournalProvider.TestJournalContainer;
     35 import android.text.TextUtils;
     36 import android.util.Log;
     37 
     38 /** Utility class which contains common code for launching activities. */
     39 public class ActivityLauncher {
     40     public static final String TAG = ActivityLauncher.class.getSimpleName();
     41 
     42     /** Key for boolean extra, indicates whether it should launch an activity. */
     43     public static final String KEY_LAUNCH_ACTIVITY = "launch_activity";
     44     /**
     45      * Key for boolean extra, indicates whether it the activity should be launched to side in
     46      * split-screen.
     47      */
     48     public static final String KEY_LAUNCH_TO_SIDE = "launch_to_the_side";
     49     /**
     50      * Key for boolean extra, indicates if launch intent should include random data to be different
     51      * from other launch intents.
     52      */
     53     public static final String KEY_RANDOM_DATA = "random_data";
     54     /**
     55      * Key for boolean extra, indicates if launch intent should have
     56      * {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
     57      */
     58     public static final String KEY_NEW_TASK = "new_task";
     59     /**
     60      * Key for boolean extra, indicates if launch intent should have
     61      * {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}.
     62      */
     63     public static final String KEY_MULTIPLE_TASK = "multiple_task";
     64     /**
     65      * Key for boolean extra, indicates if launch intent should have
     66      * {@link Intent#FLAG_ACTIVITY_REORDER_TO_FRONT}.
     67      */
     68     public static final String KEY_REORDER_TO_FRONT = "reorder_to_front";
     69     /**
     70      * Key for string extra with string representation of target component.
     71      */
     72     public static final String KEY_TARGET_COMPONENT = "target_component";
     73     /**
     74      * Key for int extra with target display id where the activity should be launched. Adding this
     75      * automatically applies {@link Intent#FLAG_ACTIVITY_NEW_TASK} and
     76      * {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to the intent.
     77      */
     78     public static final String KEY_DISPLAY_ID = "display_id";
     79     /**
     80      * Key for boolean extra, indicates if launch should be done from application context of the one
     81      * passed in {@link #launchActivityFromExtras(Context, Bundle)}.
     82      */
     83     public static final String KEY_USE_APPLICATION_CONTEXT = "use_application_context";
     84     /**
     85      * Key for boolean extra, indicates if instrumentation context will be used for launch. This
     86      * means that {@link PendingIntent} should be used instead of a regular one, because application
     87      * switch will not be allowed otherwise.
     88      */
     89     public static final String KEY_USE_INSTRUMENTATION = "use_instrumentation";
     90     /**
     91      * Key for boolean extra, indicates if any exceptions thrown during launch other then
     92      * {@link SecurityException} should be suppressed. A {@link SecurityException} is never thrown,
     93      * it's always written to logs.
     94      */
     95     public static final String KEY_SUPPRESS_EXCEPTIONS = "suppress_exceptions";
     96     /**
     97      * Key for boolean extra, indicates the result of
     98      * {@link ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
     99      */
    100     public static final String KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY =
    101             "is_activity_start_allowed_on_display";
    102     /**
    103      * Key for boolean extra, indicates a security exception is caught when launching activity by
    104      * {@link #launchActivityFromExtras}.
    105      */
    106     private static final String KEY_CAUGHT_SECURITY_EXCEPTION = "caught_security_exception";
    107     /**
    108      * Key for int extra with target activity type where activity should be launched as.
    109      */
    110     public static final String KEY_ACTIVITY_TYPE = "activity_type";
    111     /**
    112      * Key for int extra with intent flags which are used for launching an activity.
    113      */
    114     public static final String KEY_INTENT_FLAGS = "intent_flags";
    115     /**
    116      * Key for boolean extra, indicates if need to automatically applies
    117      * {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to
    118      * the intent when target display id set.
    119      */
    120     public static final String KEY_MULTIPLE_INSTANCES = "multiple_instances";
    121 
    122     /**
    123      * Key for bundle extra to the intent which are used for launching an activity.
    124      */
    125     public static final String KEY_INTENT_EXTRAS = "intent_extras";
    126 
    127 
    128     /** Perform an activity launch configured by provided extras. */
    129     public static void launchActivityFromExtras(final Context context, Bundle extras) {
    130         launchActivityFromExtras(context, extras, null /* launchInjector */);
    131     }
    132 
    133     public static void launchActivityFromExtras(final Context context, Bundle extras,
    134             LaunchInjector launchInjector) {
    135         if (extras == null || !extras.getBoolean(KEY_LAUNCH_ACTIVITY)) {
    136             return;
    137         }
    138 
    139         Log.i(TAG, "launchActivityFromExtras: extras=" + extras);
    140 
    141         final String targetComponent = extras.getString(KEY_TARGET_COMPONENT);
    142         final Intent newIntent = new Intent().setComponent(TextUtils.isEmpty(targetComponent)
    143                 ? TEST_ACTIVITY : ComponentName.unflattenFromString(targetComponent));
    144 
    145         if (extras.getBoolean(KEY_LAUNCH_TO_SIDE)) {
    146             newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT);
    147             if (extras.getBoolean(KEY_RANDOM_DATA)) {
    148                 final Uri data = new Uri.Builder()
    149                         .path(String.valueOf(System.currentTimeMillis()))
    150                         .build();
    151                 newIntent.setData(data);
    152             }
    153         }
    154         if (extras.getBoolean(KEY_MULTIPLE_TASK)) {
    155             newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
    156         }
    157         if (extras.getBoolean(KEY_NEW_TASK)) {
    158             newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
    159         }
    160 
    161         if (extras.getBoolean(KEY_REORDER_TO_FRONT)) {
    162             newIntent.addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);
    163         }
    164 
    165         final Bundle intentExtras = extras.getBundle(KEY_INTENT_EXTRAS) ;
    166         if (intentExtras != null) {
    167             newIntent.putExtras(intentExtras);
    168         }
    169 
    170         ActivityOptions options = null;
    171         final int displayId = extras.getInt(KEY_DISPLAY_ID, -1);
    172         if (displayId != -1) {
    173             options = ActivityOptions.makeBasic();
    174             options.setLaunchDisplayId(displayId);
    175             if (extras.getBoolean(KEY_MULTIPLE_INSTANCES, true)) {
    176                 newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
    177             }
    178         }
    179         if (launchInjector != null) {
    180             launchInjector.setupIntent(newIntent);
    181         }
    182         final int activityType = extras.getInt(KEY_ACTIVITY_TYPE, -1);
    183         if (activityType != -1) {
    184             if (options == null) {
    185                 options = ActivityOptions.makeBasic();
    186             }
    187             options.setLaunchActivityType(activityType);
    188         }
    189         final int intentFlags = extras.getInt(KEY_INTENT_FLAGS); // 0 if key doesn't exist.
    190         if (intentFlags != 0) {
    191             newIntent.addFlags(intentFlags);
    192         }
    193         final Bundle optionsBundle = options != null ? options.toBundle() : null;
    194 
    195         final Context launchContext = extras.getBoolean(KEY_USE_APPLICATION_CONTEXT) ?
    196                 context.getApplicationContext() : context;
    197 
    198         try {
    199             if (extras.getBoolean(KEY_USE_INSTRUMENTATION)) {
    200                 // Using PendingIntent for Instrumentation launches, because otherwise we won't
    201                 // be allowed to switch the current activity with ours with different uid.
    202                 // android.permission.STOP_APP_SWITCHES is needed to do this directly.
    203                 // PendingIntent.FLAG_CANCEL_CURRENT is needed here, or we may get an existing
    204                 // PendingIntent if it is same kind of PendingIntent request to previous one.
    205                 // Note: optionsBundle is not taking into account for PendingIntentRecord.Key
    206                 // hashcode calculation.
    207                 final PendingIntent pendingIntent = PendingIntent.getActivity(launchContext, 0,
    208                         newIntent, PendingIntent.FLAG_CANCEL_CURRENT, optionsBundle);
    209                 pendingIntent.send();
    210             } else {
    211                 launchContext.startActivity(newIntent, optionsBundle);
    212             }
    213         } catch (SecurityException e) {
    214             handleSecurityException(context, e);
    215         } catch (PendingIntent.CanceledException e) {
    216             if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) {
    217                 Log.e(TAG, "Exception launching activity with pending intent");
    218             } else {
    219                 throw new RuntimeException(e);
    220             }
    221             // Bypass the exception although it is not SecurityException.
    222             handleSecurityException(context, e);
    223         } catch (Exception e) {
    224             if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) {
    225                 Log.e(TAG, "Exception launching activity");
    226             } else {
    227                 throw e;
    228             }
    229         }
    230     }
    231 
    232     public static void checkActivityStartOnDisplay(Context context, int displayId,
    233             ComponentName componentName) {
    234         final Intent launchIntent = new Intent(Intent.ACTION_VIEW).setComponent(componentName);
    235 
    236         final boolean isAllowed = context.getSystemService(ActivityManager.class)
    237                 .isActivityStartAllowedOnDisplay(context, displayId, launchIntent);
    238         Log.i(TAG, "isActivityStartAllowedOnDisplay=" + isAllowed);
    239         TestJournalProvider.putExtras(context, TAG, bundle -> {
    240             bundle.putBoolean(KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY, isAllowed);
    241         });
    242     }
    243 
    244     public static void handleSecurityException(Context context, Exception e) {
    245         Log.e(TAG, "SecurityException launching activity: " + e);
    246         TestJournalProvider.putExtras(context, TAG, bundle -> {
    247             bundle.putBoolean(KEY_CAUGHT_SECURITY_EXCEPTION, true);
    248         });
    249     }
    250 
    251     static boolean hasCaughtSecurityException() {
    252         return TestJournalContainer.get(TAG).extras.containsKey(KEY_CAUGHT_SECURITY_EXCEPTION);
    253     }
    254 }
    255