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