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.Manifest.permission.REAL_GET_TASKS;
20 import static android.os.Build.VERSION_CODES.Q;
21 import static android.os.Build.VERSION_CODES.S;
22 
23 import static com.android.bedstead.permissions.CommonPermissions.MANAGE_ACTIVITY_STACKS;
24 import static com.android.bedstead.permissions.CommonPermissions.MANAGE_ACTIVITY_TASKS;
25 
26 import android.annotation.TargetApi;
27 import android.app.ActivityManager;
28 import android.content.ComponentName;
29 import android.content.Intent;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ResolveInfo;
32 import android.cts.testapisreflection.ActivityTaskManagerProxy;
33 import android.cts.testapisreflection.TestApisReflectionKt;
34 import android.view.Display;
35 
36 import androidx.annotation.Nullable;
37 
38 import com.android.bedstead.nene.TestApis;
39 import com.android.bedstead.nene.annotations.Experimental;
40 import com.android.bedstead.nene.exceptions.AdbException;
41 import com.android.bedstead.nene.exceptions.NeneException;
42 import com.android.bedstead.nene.packages.ComponentReference;
43 import com.android.bedstead.permissions.PermissionContext;
44 import com.android.bedstead.nene.utils.ShellCommand;
45 import com.android.bedstead.nene.utils.Versions;
46 
47 import java.util.List;
48 import java.util.stream.Collectors;
49 
50 public final class Activities {
51 
52     public static final Activities sInstance = new Activities();
53 
54     /** See {@code android.app.WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} */
55     private static final int ACTIVITY_TYPE_UNDEFINED = 0;
56     /** See {@code android.app.WindowConfiguration#ACTIVITY_TYPE_STANDARD} */
57     private static final int ACTIVITY_TYPE_STANDARD = 1;
58     /** See {@code android.app.WindowConfiguration#ACTIVITY_TYPE_HOME} */
59     private static final int ACTIVITY_TYPE_HOME = 2;
60     /** See {@code android.app.WindowConfiguration#ACTIVITY_TYPE_RECENTS} */
61     private static final int ACTIVITY_TYPE_RECENTS = 3;
62     /** See {@code android.app.WindowConfiguration#ACTIVITY_TYPE_ASSISTANT} */
63     private static final int ACTIVITY_TYPE_ASSISTANT = 4;
64     /** See {@code android.app.WindowConfiguration#ACTIVITY_TYPE_DREAM} */
65     private static final int ACTIVITY_TYPE_DREAM = 5;
66 
67     /** Proxy class to access inaccessible TestApi methods. */
68     private static final ActivityTaskManagerProxy sProxyInstance =
69             new ActivityTaskManagerProxy();
70 
Activities()71     private Activities() {
72     }
73 
74 
75     /**
76      * Wrap the given {@link NeneActivity} subclass to use Nene APIs.
77      */
wrap(Class<E> clazz, E activity)78     public <E extends NeneActivity> Activity<E> wrap(Class<E> clazz, E activity) {
79         return new Activity<>(activity, activity);
80     }
81 
82     /**
83      * Wrap the given {@link android.app.Activity} to use Nene APIs.
84      */
wrap(android.app.Activity activity)85     public LocalActivity wrap(android.app.Activity activity) {
86         return new LocalActivity(activity);
87     }
88 
89     /**
90      * Get the {@link ComponentReference} instances for each activity at the top of a recent task.
91      *
92      * <p>This is ordered from most recent to least recent and only includes tasks on the
93      * default display.
94      */
95     @Experimental
96     @TargetApi(Q)
recentActivities()97     public List<ComponentReference> recentActivities() {
98         Versions.requireMinimumVersion(Q);
99 
100         try (PermissionContext p = TestApis.permissions().withPermission(REAL_GET_TASKS)) {
101             ActivityManager activityManager =
102                     TestApis.context().instrumentedContext().getSystemService(
103                             ActivityManager.class);
104             return activityManager.getRunningTasks(100).stream()
105                     .filter(r -> getDisplayId(r) == Display.DEFAULT_DISPLAY)
106                     .map(r -> new ComponentReference(r.topActivity))
107                     .collect(Collectors.toList());
108         }
109     }
110 
getDisplayId(ActivityManager.RunningTaskInfo task)111     private int getDisplayId(ActivityManager.RunningTaskInfo task) {
112         if (Versions.meetsMinimumSdkVersionRequirement(Versions.U)) {
113             return TestApisReflectionKt.getDisplayId(task);
114         }
115 
116         return Display.DEFAULT_DISPLAY;
117     }
118 
119     /**
120      * Get the {@link ComponentReference} of the activity currently in the foreground of the default
121      * display.
122      */
123     @Experimental
124     @Nullable
foregroundActivity()125     public ComponentReference foregroundActivity() {
126         if (!Versions.meetsMinimumSdkVersionRequirement(Q)) {
127             return foregroundActivityPreQ();
128         }
129         return recentActivities().stream().findFirst().orElse(null);
130     }
131 
foregroundActivityPreQ()132     private ComponentReference foregroundActivityPreQ() {
133         try {
134             return ShellCommand.builder("dumpsys activity top")
135                     .executeAndParseOutput((dumpsysOutput) -> {
136                         // The final ACTIVITY is the one on top
137                         String[] activitySplits = dumpsysOutput.split("ACTIVITY ");
138                         String component = activitySplits[activitySplits.length - 1]
139                                 .split(" ", 2)[0];
140                         ComponentName componentName = ComponentName.unflattenFromString(component);
141                         return new ComponentReference(componentName);
142                     });
143         } catch (AdbException | RuntimeException e) {
144             throw new NeneException("Error getting foreground activity pre Q", e);
145         }
146     }
147 
148     /**
149      * Return the current state of task locking. The three possible outcomes
150      * are {@link ActivityManager#LOCK_TASK_MODE_NONE},
151      * {@link ActivityManager#LOCK_TASK_MODE_LOCKED}
152      * and {@link ActivityManager#LOCK_TASK_MODE_PINNED}.
153      */
154     @Experimental
getLockTaskModeState()155     public int getLockTaskModeState() {
156         ActivityManager activityManager =
157                 TestApis.context().instrumentedContext().getSystemService(
158                         ActivityManager.class);
159 
160         return activityManager.getLockTaskModeState();
161     }
162 
163     private final int[] ALL_ACTIVITY_TYPE_BUT_HOME = {
164             ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
165             ACTIVITY_TYPE_DREAM, ACTIVITY_TYPE_UNDEFINED
166     };
167 
168     /**
169      * Clear activities.
170      */
171     @Experimental
clearAllActivities()172     public void clearAllActivities() {
173         removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
174     }
175 
removeRootTasksWithActivityTypes(int[] activityTypes)176     private void removeRootTasksWithActivityTypes(int[] activityTypes) {
177         if (Versions.meetsMinimumSdkVersionRequirement(S)) {
178             try (PermissionContext p = TestApis.permissions().withPermission(
179                     MANAGE_ACTIVITY_TASKS)) {
180                 sProxyInstance.removeRootTasksWithActivityTypes(activityTypes);
181             }
182         } else {
183             try (PermissionContext p = TestApis.permissions().withPermission(
184                     MANAGE_ACTIVITY_STACKS)) {
185                 sProxyInstance.removeStacksWithActivityTypes(activityTypes);
186             }
187         }
188     }
189 
190     /**
191      * Get the {@link ComponentReference} of the activity the {@code intent} resolves to, if any.
192      * <p>If there's no activity with given intent, {@code Null} will be returned.
193      *
194      * @param intent The intent of the activity to be resolved
195      * @param flag   Additional flags to modify the data returned. See {@link
196      * PackageManager#resolveInfo} for details.
197      */
getResolvedActivityOfIntent(Intent intent, int flag)198     public ComponentReference getResolvedActivityOfIntent(Intent intent, int flag) {
199         ResolveInfo resolveInfo = TestApis.context().instrumentedContext()
200                 .getPackageManager().resolveActivity(intent, flag);
201 
202         if (resolveInfo == null || resolveInfo.activityInfo == null) return null;
203 
204         String activityName = (resolveInfo.activityInfo.targetActivity != null)
205                 ? resolveInfo.activityInfo.targetActivity : resolveInfo.activityInfo.name;
206 
207         if (activityName == null) {
208             return null;
209         } else {
210             return new ComponentReference(new ComponentName(resolveInfo.activityInfo.packageName,
211                     activityName
212             ));
213         }
214     }
215 }
216