1 /*
2  * Copyright (C) 2015 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.systemui.shared.system;
18 
19 import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
20 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
21 import static android.app.ActivityTaskManager.getService;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.app.Activity;
26 import android.app.ActivityClient;
27 import android.app.ActivityManager;
28 import android.app.ActivityManager.RunningTaskInfo;
29 import android.app.ActivityOptions;
30 import android.app.ActivityTaskManager;
31 import android.app.AppGlobals;
32 import android.app.WindowConfiguration;
33 import android.content.ContentResolver;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.pm.PackageManager;
37 import android.content.pm.UserInfo;
38 import android.graphics.Rect;
39 import android.os.Bundle;
40 import android.os.Handler;
41 import android.os.IBinder;
42 import android.os.RemoteException;
43 import android.os.ServiceManager;
44 import android.os.SystemClock;
45 import android.provider.Settings;
46 import android.util.Log;
47 import android.view.Display;
48 import android.view.IRecentsAnimationController;
49 import android.view.IRecentsAnimationRunner;
50 import android.view.RemoteAnimationTarget;
51 import android.window.TaskSnapshot;
52 
53 import com.android.internal.app.IVoiceInteractionManagerService;
54 import com.android.systemui.shared.recents.model.Task;
55 import com.android.systemui.shared.recents.model.ThumbnailData;
56 
57 import java.util.List;
58 import java.util.function.Consumer;
59 
60 public class ActivityManagerWrapper {
61 
62     private static final String TAG = "ActivityManagerWrapper";
63     private static final int NUM_RECENT_ACTIVITIES_REQUEST = 3;
64     private static final ActivityManagerWrapper sInstance = new ActivityManagerWrapper();
65 
66     // Should match the values in PhoneWindowManager
67     public static final String CLOSE_SYSTEM_WINDOWS_REASON_RECENTS = "recentapps";
68     public static final String CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY = "homekey";
69 
70     // Should match the value in AssistManager
71     private static final String INVOCATION_TIME_MS_KEY = "invocation_time_ms";
72 
73     private final ActivityTaskManager mAtm = ActivityTaskManager.getInstance();
ActivityManagerWrapper()74     private ActivityManagerWrapper() { }
75 
getInstance()76     public static ActivityManagerWrapper getInstance() {
77         return sInstance;
78     }
79 
80     /**
81      * @return the current user's id.
82      */
getCurrentUserId()83     public int getCurrentUserId() {
84         UserInfo ui;
85         try {
86             ui = ActivityManager.getService().getCurrentUser();
87             return ui != null ? ui.id : 0;
88         } catch (RemoteException e) {
89             throw e.rethrowFromSystemServer();
90         }
91     }
92 
93     /**
94      * @return the top running task (can be {@code null}).
95      */
getRunningTask()96     public ActivityManager.RunningTaskInfo getRunningTask() {
97         return getRunningTask(false /* filterVisibleRecents */);
98     }
99 
100     /**
101      * @return the top running task filtering only for tasks that can be visible in the recent tasks
102      * list (can be {@code null}).
103      */
getRunningTask(boolean filterOnlyVisibleRecents)104     public ActivityManager.RunningTaskInfo getRunningTask(boolean filterOnlyVisibleRecents) {
105         // Note: The set of running tasks from the system is ordered by recency
106         List<ActivityManager.RunningTaskInfo> tasks =
107                 mAtm.getTasks(1, filterOnlyVisibleRecents);
108         if (tasks.isEmpty()) {
109             return null;
110         }
111         return tasks.get(0);
112     }
113 
114     /**
115      * @see #getRunningTasks(boolean , int)
116      */
getRunningTasks(boolean filterOnlyVisibleRecents)117     public ActivityManager.RunningTaskInfo[] getRunningTasks(boolean filterOnlyVisibleRecents) {
118         return getRunningTasks(filterOnlyVisibleRecents, Display.INVALID_DISPLAY);
119     }
120 
121     /**
122      * We ask for {@link #NUM_RECENT_ACTIVITIES_REQUEST} activities because when in split screen,
123      * we'll get back 2 activities for each split app and one for launcher. Launcher might be more
124      * "recently" used than one of the split apps so if we only request 2 tasks, then we might miss
125      * out on one of the split apps
126      *
127      * @return an array of up to {@link #NUM_RECENT_ACTIVITIES_REQUEST} running tasks
128      *         filtering only for tasks that can be visible in the recent tasks list.
129      */
getRunningTasks(boolean filterOnlyVisibleRecents, int displayId)130     public ActivityManager.RunningTaskInfo[] getRunningTasks(boolean filterOnlyVisibleRecents,
131             int displayId) {
132         // Note: The set of running tasks from the system is ordered by recency
133         List<ActivityManager.RunningTaskInfo> tasks =
134                 mAtm.getTasks(NUM_RECENT_ACTIVITIES_REQUEST,
135                         filterOnlyVisibleRecents, /* keepInExtras= */ false, displayId);
136         return tasks.toArray(new RunningTaskInfo[tasks.size()]);
137     }
138 
139     /**
140      * @return the task snapshot for the given {@param taskId}.
141      */
getTaskThumbnail(int taskId, boolean isLowResolution)142     public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean isLowResolution) {
143         TaskSnapshot snapshot = null;
144         try {
145             snapshot = getService().getTaskSnapshot(taskId, isLowResolution);
146         } catch (RemoteException e) {
147             Log.w(TAG, "Failed to retrieve task snapshot", e);
148         }
149         if (snapshot != null) {
150             return ThumbnailData.fromSnapshot(snapshot);
151         } else {
152             return new ThumbnailData();
153         }
154     }
155 
156 
157     /**
158      * Requests for a new snapshot to be taken for the given task, stores it in the cache, and
159      * returns a {@link ThumbnailData} with the result.
160      */
161     @NonNull
takeTaskThumbnail(int taskId)162     public ThumbnailData takeTaskThumbnail(int taskId) {
163         TaskSnapshot snapshot = null;
164         try {
165             snapshot = getService().takeTaskSnapshot(taskId, /* updateCache= */ true);
166         } catch (RemoteException e) {
167             Log.w(TAG, "Failed to take task snapshot", e);
168         }
169         if (snapshot != null) {
170             return ThumbnailData.fromSnapshot(snapshot);
171         } else {
172             return new ThumbnailData();
173         }
174     }
175 
176     /**
177      * Removes the outdated snapshot of home task.
178      *
179      * @param homeActivity The home task activity, or null if you have the
180      *                     {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS} permission and
181      *                     want us to find the home task for you.
182      */
invalidateHomeTaskSnapshot(@ullable final Activity homeActivity)183     public void invalidateHomeTaskSnapshot(@Nullable final Activity homeActivity) {
184         try {
185             ActivityClient.getInstance().invalidateHomeTaskSnapshot(
186                     homeActivity == null ? null : homeActivity.getActivityToken());
187         } catch (Throwable e) {
188             Log.w(TAG, "Failed to invalidate home snapshot", e);
189         }
190     }
191 
192     /**
193      * Starts the recents activity. The caller should manage the thread on which this is called.
194      */
startRecentsActivity(Intent intent, long eventTime, final RecentsAnimationListener animationHandler, final Consumer<Boolean> resultCallback, Handler resultCallbackHandler)195     public void startRecentsActivity(Intent intent, long eventTime,
196             final RecentsAnimationListener animationHandler, final Consumer<Boolean> resultCallback,
197             Handler resultCallbackHandler) {
198         boolean result = startRecentsActivity(intent, eventTime, animationHandler);
199         if (resultCallback != null && resultCallbackHandler != null) {
200             resultCallbackHandler.post(new Runnable() {
201                 @Override
202                 public void run() {
203                     resultCallback.accept(result);
204                 }
205             });
206         }
207     }
208 
209     /**
210      * Starts the recents activity. The caller should manage the thread on which this is called.
211      */
startRecentsActivity( Intent intent, long eventTime, RecentsAnimationListener animationHandler)212     public boolean startRecentsActivity(
213             Intent intent, long eventTime, RecentsAnimationListener animationHandler) {
214         try {
215             IRecentsAnimationRunner runner = null;
216             if (animationHandler != null) {
217                 runner = new IRecentsAnimationRunner.Stub() {
218                     @Override
219                     public void onAnimationStart(IRecentsAnimationController controller,
220                             RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
221                             Rect homeContentInsets, Rect minimizedHomeBounds,
222                             Bundle extras) {
223                         final RecentsAnimationControllerCompat controllerCompat =
224                                 new RecentsAnimationControllerCompat(controller);
225                         animationHandler.onAnimationStart(controllerCompat, apps,
226                                 wallpapers, homeContentInsets, minimizedHomeBounds, extras);
227                     }
228 
229                     @Override
230                     public void onAnimationCanceled(int[] taskIds, TaskSnapshot[] taskSnapshots) {
231                         animationHandler.onAnimationCanceled(
232                                 ThumbnailData.wrap(taskIds, taskSnapshots));
233                     }
234 
235                     @Override
236                     public void onTasksAppeared(RemoteAnimationTarget[] apps) {
237                         animationHandler.onTasksAppeared(apps);
238                     }
239                 };
240             }
241             getService().startRecentsActivity(intent, eventTime, runner);
242             return true;
243         } catch (Exception e) {
244             return false;
245         }
246     }
247 
248     /**
249      * Cancels the remote recents animation started from {@link #startRecentsActivity}.
250      */
cancelRecentsAnimation(boolean restoreHomeRootTaskPosition)251     public void cancelRecentsAnimation(boolean restoreHomeRootTaskPosition) {
252         try {
253             getService().cancelRecentsAnimation(restoreHomeRootTaskPosition);
254         } catch (RemoteException e) {
255             Log.e(TAG, "Failed to cancel recents animation", e);
256         }
257     }
258 
259     /**
260      * Starts a task from Recents synchronously.
261      */
startActivityFromRecents(Task.TaskKey taskKey, ActivityOptions options)262     public boolean startActivityFromRecents(Task.TaskKey taskKey, ActivityOptions options) {
263         return startActivityFromRecents(taskKey.id, options);
264     }
265 
266     /**
267      * Starts a task from Recents synchronously.
268      */
startActivityFromRecents(int taskId, ActivityOptions options)269     public boolean startActivityFromRecents(int taskId, ActivityOptions options) {
270         try {
271             Bundle optsBundle = options == null ? null : options.toBundle();
272             return ActivityManager.isStartResultSuccessful(
273                     getService().startActivityFromRecents(
274                             taskId, optsBundle));
275         } catch (Exception e) {
276             return false;
277         }
278     }
279 
280     /**
281      * Requests that the system close any open system windows (including other SystemUI).
282      */
closeSystemWindows(final String reason)283     public void closeSystemWindows(final String reason) {
284         try {
285             ActivityManager.getService().closeSystemDialogs(reason);
286         } catch (RemoteException e) {
287             Log.w(TAG, "Failed to close system windows", e);
288         }
289     }
290 
291     /**
292      * Removes a task by id.
293      */
removeTask(final int taskId)294     public void removeTask(final int taskId) {
295         try {
296             getService().removeTask(taskId);
297         } catch (RemoteException e) {
298             Log.w(TAG, "Failed to remove task=" + taskId, e);
299         }
300     }
301 
302     /**
303      * Removes all the recent tasks.
304      */
removeAllRecentTasks()305     public void removeAllRecentTasks() {
306         try {
307             getService().removeAllVisibleRecentTasks();
308         } catch (RemoteException e) {
309             Log.w(TAG, "Failed to remove all tasks", e);
310         }
311     }
312 
313     /**
314      * @return whether screen pinning is enabled.
315      */
isScreenPinningEnabled()316     public boolean isScreenPinningEnabled() {
317         final ContentResolver cr = AppGlobals.getInitialApplication().getContentResolver();
318         return Settings.System.getInt(cr, Settings.System.LOCK_TO_APP_ENABLED, 0) != 0;
319     }
320 
321     /**
322      * @return whether there is currently a locked task (ie. in screen pinning).
323      */
isLockToAppActive()324     public boolean isLockToAppActive() {
325         try {
326             return getService().getLockTaskModeState() != LOCK_TASK_MODE_NONE;
327         } catch (RemoteException e) {
328             return false;
329         }
330     }
331 
332     /**
333      * @return whether lock task mode is active in kiosk-mode (not screen pinning).
334      */
isLockTaskKioskModeActive()335     public boolean isLockTaskKioskModeActive() {
336         try {
337             return getService().getLockTaskModeState() == LOCK_TASK_MODE_LOCKED;
338         } catch (RemoteException e) {
339             return false;
340         }
341     }
342 
343     /**
344      * Shows a voice session identified by {@code token}
345      * @return true if the session was shown, false otherwise
346      */
showVoiceSession(@onNull IBinder token, @NonNull Bundle args, int flags, @Nullable String attributionTag)347     public boolean showVoiceSession(@NonNull IBinder token, @NonNull Bundle args, int flags,
348             @Nullable String attributionTag) {
349         IVoiceInteractionManagerService service = IVoiceInteractionManagerService.Stub.asInterface(
350                 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
351         if (service == null) {
352             return false;
353         }
354         args.putLong(INVOCATION_TIME_MS_KEY, SystemClock.elapsedRealtime());
355 
356         try {
357             return service.showSessionFromSession(token, args, flags, attributionTag);
358         } catch (RemoteException e) {
359             return false;
360         }
361     }
362 
363     /**
364      * Returns true if the system supports freeform multi-window.
365      */
supportsFreeformMultiWindow(Context context)366     public boolean supportsFreeformMultiWindow(Context context) {
367         final boolean freeformDevOption = Settings.Global.getInt(context.getContentResolver(),
368                 Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
369         return ActivityTaskManager.supportsMultiWindow(context)
370                 && (context.getPackageManager().hasSystemFeature(
371                 PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
372                 || freeformDevOption);
373     }
374 
375     /**
376      * Returns true if the running task represents the home task
377      */
isHomeTask(RunningTaskInfo info)378     public static boolean isHomeTask(RunningTaskInfo info) {
379         return info.configuration.windowConfiguration.getActivityType()
380                 == WindowConfiguration.ACTIVITY_TYPE_HOME;
381     }
382 }
383