• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 com.android.bedstead.nene.activities;
18 
19 import static android.cts.testapisreflection.TestApisReflectionKt.getDisplayId;
20 import static android.Manifest.permission.REAL_GET_TASKS;
21 import static android.os.Build.VERSION_CODES.Q;
22 import static android.os.Build.VERSION_CODES.S;
23 
24 import static com.android.bedstead.permissions.CommonPermissions.MANAGE_ACTIVITY_STACKS;
25 import static com.android.bedstead.permissions.CommonPermissions.MANAGE_ACTIVITY_TASKS;
26 
27 import android.annotation.TargetApi;
28 import android.app.ActivityManager;
29 import android.content.ComponentName;
30 import android.content.Intent;
31 import android.content.pm.PackageManager;
32 import android.content.pm.ResolveInfo;
33 import android.cts.testapisreflection.ActivityTaskManagerProxy;
34 import android.os.Bundle;
35 import android.util.Log;
36 import android.view.Display;
37 
38 import androidx.annotation.Nullable;
39 import androidx.test.platform.app.InstrumentationRegistry;
40 
41 import com.android.bedstead.nene.TestApis;
42 import com.android.bedstead.nene.annotations.Experimental;
43 import com.android.bedstead.nene.exceptions.AdbException;
44 import com.android.bedstead.nene.exceptions.NeneException;
45 import com.android.bedstead.nene.packages.ComponentReference;
46 import com.android.bedstead.nene.utils.Poll;
47 import com.android.bedstead.nene.utils.ShellCommand;
48 import com.android.bedstead.nene.utils.Versions;
49 import com.android.bedstead.permissions.PermissionContext;
50 
51 import java.lang.reflect.InvocationTargetException;
52 import java.lang.reflect.Method;
53 import java.time.Duration;
54 import java.util.List;
55 import java.util.stream.Collectors;
56 
57 public final class Activities {
58 
59     public static final Activities sInstance = new Activities();
60 
61     /** See {@code android.app.WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} */
62     private static final int ACTIVITY_TYPE_UNDEFINED = 0;
63     /** See {@code android.app.WindowConfiguration#ACTIVITY_TYPE_STANDARD} */
64     private static final int ACTIVITY_TYPE_STANDARD = 1;
65     /** See {@code android.app.WindowConfiguration#ACTIVITY_TYPE_HOME} */
66     private static final int ACTIVITY_TYPE_HOME = 2;
67     /** See {@code android.app.WindowConfiguration#ACTIVITY_TYPE_RECENTS} */
68     private static final int ACTIVITY_TYPE_RECENTS = 3;
69     /** See {@code android.app.WindowConfiguration#ACTIVITY_TYPE_ASSISTANT} */
70     private static final int ACTIVITY_TYPE_ASSISTANT = 4;
71     /** See {@code android.app.WindowConfiguration#ACTIVITY_TYPE_DREAM} */
72     private static final int ACTIVITY_TYPE_DREAM = 5;
73 
74     /** Proxy class to access inaccessible TestApi methods. */
75     private static final ActivityTaskManagerProxy sActivityTaskManagerProxy =
76             new ActivityTaskManagerProxy();
77 
78     private static final String TAG = "BedsteadActivities";
79 
Activities()80     private Activities() {
81     }
82 
83 
84     /**
85      * Wrap the given {@link NeneActivity} subclass to use Nene APIs.
86      */
wrap(Class<E> clazz, E activity)87     public <E extends NeneActivity> Activity<E> wrap(Class<E> clazz, E activity) {
88         return new Activity<>(activity, activity);
89     }
90 
91     /**
92      * Wrap the given {@link android.app.Activity} to use Nene APIs.
93      */
wrap(android.app.Activity activity)94     public LocalActivity wrap(android.app.Activity activity) {
95         return new LocalActivity(activity);
96     }
97 
98     /**
99      * Get the {@link ComponentReference} instances for each activity at the top of a recent task.
100      *
101      * <p>This is ordered from most recent to least recent and only includes tasks on the
102      * default display.
103      */
104     @Experimental
105     @TargetApi(Q)
recentActivities()106     public List<ComponentReference> recentActivities() {
107         Versions.requireMinimumVersion(Q);
108 
109         try (PermissionContext p = TestApis.permissions().withPermission(REAL_GET_TASKS)) {
110             ActivityManager activityManager =
111                     TestApis.context().instrumentedContext().getSystemService(
112                             ActivityManager.class);
113             return activityManager.getRunningTasks(100).stream()
114                     .filter(r -> getDisplayIdInternal(r) == Display.DEFAULT_DISPLAY)
115                     .map(r -> new ComponentReference(r.topActivity))
116                     .collect(Collectors.toList());
117         }
118     }
119 
getDisplayIdInternal(ActivityManager.RunningTaskInfo task)120     private int getDisplayIdInternal(ActivityManager.RunningTaskInfo task) {
121         if (Versions.meetsMinimumSdkVersionRequirement(Versions.U)) {
122             return getDisplayId(task);
123         }
124 
125         return Display.DEFAULT_DISPLAY;
126     }
127 
128     /**
129      * Get the {@link ComponentReference} of the activity currently in the foreground of the default
130      * display.
131      */
132     @Experimental
133     @Nullable
foregroundActivity()134     public ComponentReference foregroundActivity() {
135         if (!Versions.meetsMinimumSdkVersionRequirement(Q)) {
136             return foregroundActivityPreQ();
137         }
138         return recentActivities().stream().findFirst().orElse(null);
139     }
140 
foregroundActivityPreQ()141     private ComponentReference foregroundActivityPreQ() {
142         try {
143             return ShellCommand.builder("dumpsys activity top")
144                     .executeAndParseOutput((dumpsysOutput) -> {
145                         // The final ACTIVITY is the one on top
146                         String[] activitySplits = dumpsysOutput.split("ACTIVITY ");
147                         String component = activitySplits[activitySplits.length - 1]
148                                 .split(" ", 2)[0];
149                         ComponentName componentName = ComponentName.unflattenFromString(component);
150                         return new ComponentReference(componentName);
151                     });
152         } catch (AdbException | RuntimeException e) {
153             throw new NeneException("Error getting foreground activity pre Q", e);
154         }
155     }
156 
157     /**
158      * Return the current state of task locking. The three possible outcomes
159      * are {@link ActivityManager#LOCK_TASK_MODE_NONE},
160      * {@link ActivityManager#LOCK_TASK_MODE_LOCKED}
161      * and {@link ActivityManager#LOCK_TASK_MODE_PINNED}.
162      */
163     @Experimental
getLockTaskModeState()164     public int getLockTaskModeState() {
165         ActivityManager activityManager =
166                 TestApis.context().instrumentedContext().getSystemService(
167                         ActivityManager.class);
168 
169         return activityManager.getLockTaskModeState();
170     }
171 
172     private final int[] ALL_ACTIVITY_TYPE_BUT_HOME = {
173             ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
174             ACTIVITY_TYPE_DREAM, ACTIVITY_TYPE_UNDEFINED
175     };
176 
177     /**
178      * Clear activities.
179      */
180     @Experimental
clearAllActivities()181     public void clearAllActivities() {
182         removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
183     }
184 
removeRootTasksWithActivityTypes(int[] activityTypes)185     private void removeRootTasksWithActivityTypes(int[] activityTypes) {
186         if (Versions.meetsMinimumSdkVersionRequirement(S)) {
187             try (PermissionContext p = TestApis.permissions().withPermission(
188                     MANAGE_ACTIVITY_TASKS)) {
189                 sActivityTaskManagerProxy.removeRootTasksWithActivityTypes(activityTypes);
190             }
191         } else {
192             try (PermissionContext p = TestApis.permissions().withPermission(
193                     MANAGE_ACTIVITY_STACKS)) {
194                 // This should have been a proxy call through ActivityTaskManagerProxy as well, but
195                 // is not since ActivityTaskManager#removeStacksWithActivityTypes is not available
196                 // to be fetched and proxied in Versions S+.
197                 Method method = Class.forName("android.app.ActivityTaskManager")
198                         .getDeclaredMethod("removeStacksWithActivityTypes",
199                         new Class<?>[]{ int[].class });
200                 method.invoke(TestApis.context().instrumentedContext().getSystemService(
201                         Class.forName("android.app.ActivityTaskManager")),
202                         ALL_ACTIVITY_TYPE_BUT_HOME);
203             } catch (NoSuchMethodException | IllegalAccessException |
204                      InvocationTargetException | ClassNotFoundException e) {
205                 throw new NeneException("Error clearing all activities activity pre S", e);
206             }
207         }
208     }
209 
210     /**
211      * Get the {@link ComponentReference} of the activity the {@code intent} resolves to, if any.
212      * <p>If there's no activity with given intent, {@code Null} will be returned.
213      *
214      * @param intent The intent of the activity to be resolved
215      * @param flag   Additional flags to modify the data returned. See {@link
216      * PackageManager#resolveInfo} for details.
217      */
getResolvedActivityOfIntent(Intent intent, int flag)218     public ComponentReference getResolvedActivityOfIntent(Intent intent, int flag) {
219         ResolveInfo resolveInfo = TestApis.context().instrumentedContext()
220                 .getPackageManager().resolveActivity(intent, flag);
221 
222         if (resolveInfo == null || resolveInfo.activityInfo == null) return null;
223 
224         String activityName = (resolveInfo.activityInfo.targetActivity != null)
225                 ? resolveInfo.activityInfo.targetActivity : resolveInfo.activityInfo.name;
226 
227         if (activityName == null) {
228             return null;
229         } else {
230             return new ComponentReference(new ComponentName(resolveInfo.activityInfo.packageName,
231                     activityName
232             ));
233         }
234     }
235 
236     /**
237      * Blocks until the activity has started.
238      *
239      * @param intent The intent to start.
240      */
startActivity(Intent intent)241     public void startActivity(Intent intent) {
242         startActivity(intent, null);
243     }
244 
245     /**
246      * Blocks until the activity has started.
247      *
248      * @param intent The intent to start.
249      * @param options Additional options for how the Activity should be started.
250      * See {@link android.content.Context#startActivity(Intent, Bundle)}
251      * Context.startActivity(Intent, Bundle)} for more details.
252      */
startActivity(Intent intent, @Nullable Bundle options)253     public void startActivity(Intent intent, @Nullable Bundle options) {
254         Log.d(TAG, "startActivity(): " + intent + ", " + options);
255         ComponentReference startActivity = TestApis.activities().foregroundActivity();
256         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
257         InstrumentationRegistry.getInstrumentation().getContext().startActivity(intent, options);
258         Poll.forValue("Foreground activity", () -> TestApis.activities().foregroundActivity())
259                 .toNotBeEqualTo(startActivity)
260                 .errorOnFail("Could not start activity " + this + ". Relevant logcat: "
261                     + TestApis.logcat().dump(l -> l.contains("ActivityManager")))
262                 .timeout(Duration.ofSeconds(30))
263                 .await();
264     }
265 }
266