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.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
24 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
25 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
26 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
27 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
28 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
29 
30 import android.annotation.NonNull;
31 import android.app.ActivityManager;
32 import android.app.ActivityManager.RecentTaskInfo;
33 import android.app.ActivityOptions;
34 import android.app.ActivityTaskManager;
35 import android.app.AppGlobals;
36 import android.app.IAssistDataReceiver;
37 import android.app.WindowConfiguration.ActivityType;
38 import android.content.ContentResolver;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.pm.ActivityInfo;
42 import android.content.pm.ApplicationInfo;
43 import android.content.pm.PackageManager;
44 import android.content.pm.UserInfo;
45 import android.graphics.Bitmap;
46 import android.graphics.Rect;
47 import android.os.Bundle;
48 import android.os.Handler;
49 import android.os.IBinder;
50 import android.os.Looper;
51 import android.os.RemoteException;
52 import android.os.ServiceManager;
53 import android.os.UserHandle;
54 import android.provider.Settings;
55 import android.util.Log;
56 import android.view.IRecentsAnimationController;
57 import android.view.IRecentsAnimationRunner;
58 import android.view.RemoteAnimationTarget;
59 
60 import com.android.internal.app.IVoiceInteractionManagerService;
61 import com.android.systemui.shared.recents.model.Task;
62 import com.android.systemui.shared.recents.model.Task.TaskKey;
63 import com.android.systemui.shared.recents.model.ThumbnailData;
64 
65 import java.util.ArrayList;
66 import java.util.List;
67 import java.util.concurrent.Future;
68 import java.util.function.Consumer;
69 
70 public class ActivityManagerWrapper {
71 
72     private static final String TAG = "ActivityManagerWrapper";
73 
74     private static final ActivityManagerWrapper sInstance = new ActivityManagerWrapper();
75 
76     // Should match the values in PhoneWindowManager
77     public static final String CLOSE_SYSTEM_WINDOWS_REASON_RECENTS = "recentapps";
78 
79     private final PackageManager mPackageManager;
80     private final BackgroundExecutor mBackgroundExecutor;
81     private final TaskStackChangeListeners mTaskStackChangeListeners;
82 
ActivityManagerWrapper()83     private ActivityManagerWrapper() {
84         final Context context = AppGlobals.getInitialApplication();
85         mPackageManager = context.getPackageManager();
86         mBackgroundExecutor = BackgroundExecutor.get();
87         mTaskStackChangeListeners = new TaskStackChangeListeners(Looper.getMainLooper());
88     }
89 
getInstance()90     public static ActivityManagerWrapper getInstance() {
91         return sInstance;
92     }
93 
94     /**
95      * @return the current user's id.
96      */
getCurrentUserId()97     public int getCurrentUserId() {
98         UserInfo ui;
99         try {
100             ui = ActivityManager.getService().getCurrentUser();
101             return ui != null ? ui.id : 0;
102         } catch (RemoteException e) {
103             throw e.rethrowFromSystemServer();
104         }
105     }
106 
107     /**
108      * @return the top running task (can be {@code null}).
109      */
getRunningTask()110     public ActivityManager.RunningTaskInfo getRunningTask() {
111         return getRunningTask(ACTIVITY_TYPE_RECENTS /* ignoreActivityType */);
112     }
113 
getRunningTask(@ctivityType int ignoreActivityType)114     public ActivityManager.RunningTaskInfo getRunningTask(@ActivityType int ignoreActivityType) {
115         // Note: The set of running tasks from the system is ordered by recency
116         try {
117             List<ActivityManager.RunningTaskInfo> tasks =
118                     ActivityTaskManager.getService().getFilteredTasks(1, ignoreActivityType,
119                             WINDOWING_MODE_PINNED /* ignoreWindowingMode */);
120             if (tasks.isEmpty()) {
121                 return null;
122             }
123             return tasks.get(0);
124         } catch (RemoteException e) {
125             return null;
126         }
127     }
128 
129     /**
130      * @return a list of the recents tasks.
131      */
getRecentTasks(int numTasks, int userId)132     public List<RecentTaskInfo> getRecentTasks(int numTasks, int userId) {
133         try {
134             return ActivityTaskManager.getService().getRecentTasks(numTasks,
135                             RECENT_IGNORE_UNAVAILABLE, userId).getList();
136         } catch (RemoteException e) {
137             Log.e(TAG, "Failed to get recent tasks", e);
138             return new ArrayList<>();
139         }
140     }
141 
142     /**
143      * @return the task snapshot for the given {@param taskId}.
144      */
getTaskThumbnail(int taskId, boolean reducedResolution)145     public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean reducedResolution) {
146         ActivityManager.TaskSnapshot snapshot = null;
147         try {
148             snapshot = ActivityTaskManager.getService().getTaskSnapshot(taskId, reducedResolution);
149         } catch (RemoteException e) {
150             Log.w(TAG, "Failed to retrieve task snapshot", e);
151         }
152         if (snapshot != null) {
153             return new ThumbnailData(snapshot);
154         } else {
155             return new ThumbnailData();
156         }
157     }
158 
159     /**
160      * @return the activity label, badging if necessary.
161      */
getBadgedActivityLabel(ActivityInfo info, int userId)162     public String getBadgedActivityLabel(ActivityInfo info, int userId) {
163         return getBadgedLabel(info.loadLabel(mPackageManager).toString(), userId);
164     }
165 
166     /**
167      * @return the application label, badging if necessary.
168      */
getBadgedApplicationLabel(ApplicationInfo appInfo, int userId)169     public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) {
170         return getBadgedLabel(appInfo.loadLabel(mPackageManager).toString(), userId);
171     }
172 
173     /**
174      * @return the content description for a given task, badging it if necessary.  The content
175      * description joins the app and activity labels.
176      */
getBadgedContentDescription(ActivityInfo info, int userId, ActivityManager.TaskDescription td)177     public String getBadgedContentDescription(ActivityInfo info, int userId,
178             ActivityManager.TaskDescription td) {
179         String activityLabel;
180         if (td != null && td.getLabel() != null) {
181             activityLabel = td.getLabel();
182         } else {
183             activityLabel = info.loadLabel(mPackageManager).toString();
184         }
185         String applicationLabel = info.applicationInfo.loadLabel(mPackageManager).toString();
186         String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId);
187         return applicationLabel.equals(activityLabel)
188                 ? badgedApplicationLabel
189                 : badgedApplicationLabel + " " + activityLabel;
190     }
191 
192     /**
193      * @return the given label for a user, badging if necessary.
194      */
getBadgedLabel(String label, int userId)195     private String getBadgedLabel(String label, int userId) {
196         if (userId != UserHandle.myUserId()) {
197             label = mPackageManager.getUserBadgedLabel(label, new UserHandle(userId)).toString();
198         }
199         return label;
200     }
201 
202     /**
203      * Starts the recents activity. The caller should manage the thread on which this is called.
204      */
startRecentsActivity(Intent intent, final AssistDataReceiver assistDataReceiver, final RecentsAnimationListener animationHandler, final Consumer<Boolean> resultCallback, Handler resultCallbackHandler)205     public void startRecentsActivity(Intent intent, final AssistDataReceiver assistDataReceiver,
206             final RecentsAnimationListener animationHandler, final Consumer<Boolean> resultCallback,
207             Handler resultCallbackHandler) {
208         try {
209             IAssistDataReceiver receiver = null;
210             if (assistDataReceiver != null) {
211                 receiver = new IAssistDataReceiver.Stub() {
212                     public void onHandleAssistData(Bundle resultData) {
213                         assistDataReceiver.onHandleAssistData(resultData);
214                     }
215                     public void onHandleAssistScreenshot(Bitmap screenshot) {
216                         assistDataReceiver.onHandleAssistScreenshot(screenshot);
217                     }
218                 };
219             }
220             IRecentsAnimationRunner runner = null;
221             if (animationHandler != null) {
222                 runner = new IRecentsAnimationRunner.Stub() {
223                     @Override
224                     public void onAnimationStart(IRecentsAnimationController controller,
225                             RemoteAnimationTarget[] apps, Rect homeContentInsets,
226                             Rect minimizedHomeBounds) {
227                         final RecentsAnimationControllerCompat controllerCompat =
228                                 new RecentsAnimationControllerCompat(controller);
229                         final RemoteAnimationTargetCompat[] appsCompat =
230                                 RemoteAnimationTargetCompat.wrap(apps);
231                         animationHandler.onAnimationStart(controllerCompat, appsCompat,
232                                 homeContentInsets, minimizedHomeBounds);
233                     }
234 
235                     @Override
236                     public void onAnimationCanceled(boolean deferredWithScreenshot) {
237                         animationHandler.onAnimationCanceled(deferredWithScreenshot);
238                     }
239                 };
240             }
241             ActivityTaskManager.getService().startRecentsActivity(intent, receiver, runner);
242             if (resultCallback != null) {
243                 resultCallbackHandler.post(new Runnable() {
244                     @Override
245                     public void run() {
246                         resultCallback.accept(true);
247                     }
248                 });
249             }
250         } catch (Exception e) {
251             if (resultCallback != null) {
252                 resultCallbackHandler.post(new Runnable() {
253                     @Override
254                     public void run() {
255                         resultCallback.accept(false);
256                     }
257                 });
258             }
259         }
260     }
261 
262     /**
263      * Cancels the remote recents animation started from {@link #startRecentsActivity}.
264      */
cancelRecentsAnimation(boolean restoreHomeStackPosition)265     public void cancelRecentsAnimation(boolean restoreHomeStackPosition) {
266         try {
267             ActivityTaskManager.getService().cancelRecentsAnimation(restoreHomeStackPosition);
268         } catch (RemoteException e) {
269             Log.e(TAG, "Failed to cancel recents animation", e);
270         }
271     }
272 
273     /**
274      * Starts a task from Recents.
275      *
276      * @see {@link #startActivityFromRecentsAsync(TaskKey, ActivityOptions, int, int, Consumer, Handler)}
277      */
startActivityFromRecentsAsync(Task.TaskKey taskKey, ActivityOptions options, Consumer<Boolean> resultCallback, Handler resultCallbackHandler)278     public void startActivityFromRecentsAsync(Task.TaskKey taskKey, ActivityOptions options,
279             Consumer<Boolean> resultCallback, Handler resultCallbackHandler) {
280         startActivityFromRecentsAsync(taskKey, options, WINDOWING_MODE_UNDEFINED,
281                 ACTIVITY_TYPE_UNDEFINED, resultCallback, resultCallbackHandler);
282     }
283 
284     /**
285      * Starts a task from Recents.
286      *
287      * @param resultCallback The result success callback
288      * @param resultCallbackHandler The handler to receive the result callback
289      */
startActivityFromRecentsAsync(final Task.TaskKey taskKey, ActivityOptions options, int windowingMode, int activityType, final Consumer<Boolean> resultCallback, final Handler resultCallbackHandler)290     public void startActivityFromRecentsAsync(final Task.TaskKey taskKey, ActivityOptions options,
291             int windowingMode, int activityType, final Consumer<Boolean> resultCallback,
292             final Handler resultCallbackHandler) {
293         if (taskKey.windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
294             // We show non-visible docked tasks in Recents, but we always want to launch
295             // them in the fullscreen stack.
296             if (options == null) {
297                 options = ActivityOptions.makeBasic();
298             }
299             options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
300         } else if (windowingMode != WINDOWING_MODE_UNDEFINED
301                 || activityType != ACTIVITY_TYPE_UNDEFINED) {
302             if (options == null) {
303                 options = ActivityOptions.makeBasic();
304             }
305             options.setLaunchWindowingMode(windowingMode);
306             options.setLaunchActivityType(activityType);
307         }
308         final ActivityOptions finalOptions = options;
309 
310         // Execute this from another thread such that we can do other things (like caching the
311         // bitmap for the thumbnail) while AM is busy starting our activity.
312         mBackgroundExecutor.submit(new Runnable() {
313             @Override
314             public void run() {
315                 boolean result = false;
316                 try {
317                     result = startActivityFromRecents(taskKey.id, finalOptions);
318                 } catch (Exception e) {
319                     // Fall through
320                 }
321                 final boolean finalResult = result;
322                 if (resultCallback != null) {
323                     resultCallbackHandler.post(new Runnable() {
324                         @Override
325                         public void run() {
326                             resultCallback.accept(finalResult);
327                         }
328                     });
329                 }
330             }
331         });
332     }
333 
334     /**
335      * Starts a task from Recents synchronously.
336      */
startActivityFromRecents(int taskId, ActivityOptions options)337     public boolean startActivityFromRecents(int taskId, ActivityOptions options) {
338         try {
339             Bundle optsBundle = options == null ? null : options.toBundle();
340             ActivityTaskManager.getService().startActivityFromRecents(taskId, optsBundle);
341             return true;
342         } catch (Exception e) {
343             return false;
344         }
345     }
346 
347     /**
348      * Moves an already resumed task to the side of the screen to initiate split screen.
349      */
setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, Rect initialBounds)350     public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode,
351             Rect initialBounds) {
352         try {
353             return ActivityTaskManager.getService().setTaskWindowingModeSplitScreenPrimary(taskId,
354                     createMode, true /* onTop */, false /* animate */, initialBounds,
355                     true /* showRecents */);
356         } catch (RemoteException e) {
357             return false;
358         }
359     }
360 
361     /**
362      * Registers a task stack listener with the system.
363      * This should be called on the main thread.
364      */
registerTaskStackListener(TaskStackChangeListener listener)365     public void registerTaskStackListener(TaskStackChangeListener listener) {
366         synchronized (mTaskStackChangeListeners) {
367             mTaskStackChangeListeners.addListener(ActivityManager.getService(), listener);
368         }
369     }
370 
371     /**
372      * Unregisters a task stack listener with the system.
373      * This should be called on the main thread.
374      */
unregisterTaskStackListener(TaskStackChangeListener listener)375     public void unregisterTaskStackListener(TaskStackChangeListener listener) {
376         synchronized (mTaskStackChangeListeners) {
377             mTaskStackChangeListeners.removeListener(listener);
378         }
379     }
380 
381     /**
382      * Requests that the system close any open system windows (including other SystemUI).
383      */
closeSystemWindows(final String reason)384     public Future<?> closeSystemWindows(final String reason) {
385         return mBackgroundExecutor.submit(new Runnable() {
386             @Override
387             public void run() {
388                 try {
389                     ActivityManager.getService().closeSystemDialogs(reason);
390                 } catch (RemoteException e) {
391                     Log.w(TAG, "Failed to close system windows", e);
392                 }
393             }
394         });
395     }
396 
397     /**
398      * Removes a task by id.
399      */
400     public void removeTask(final int taskId) {
401         mBackgroundExecutor.submit(new Runnable() {
402             @Override
403             public void run() {
404                 try {
405                     ActivityTaskManager.getService().removeTask(taskId);
406                 } catch (RemoteException e) {
407                     Log.w(TAG, "Failed to remove task=" + taskId, e);
408                 }
409             }
410         });
411     }
412 
413     /**
414      * Removes all the recent tasks.
415      */
416     public void removeAllRecentTasks() {
417         mBackgroundExecutor.submit(new Runnable() {
418             @Override
419             public void run() {
420                 try {
421                     ActivityTaskManager.getService().removeAllVisibleRecentTasks();
422                 } catch (RemoteException e) {
423                     Log.w(TAG, "Failed to remove all tasks", e);
424                 }
425             }
426         });
427     }
428 
429     /**
430      * Cancels the current window transtion to/from Recents for the given task id.
431      */
432     public void cancelWindowTransition(int taskId) {
433         try {
434             ActivityTaskManager.getService().cancelTaskWindowTransition(taskId);
435         } catch (RemoteException e) {
436             Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
437         }
438     }
439 
440     /**
441      * @return whether screen pinning is active.
442      */
443     public boolean isScreenPinningActive() {
444         try {
445             return ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED;
446         } catch (RemoteException e) {
447             return false;
448         }
449     }
450 
451     /**
452      * @return whether screen pinning is enabled.
453      */
454     public boolean isScreenPinningEnabled() {
455         final ContentResolver cr = AppGlobals.getInitialApplication().getContentResolver();
456         return Settings.System.getInt(cr, Settings.System.LOCK_TO_APP_ENABLED, 0) != 0;
457     }
458 
459     /**
460      * @return whether there is currently a locked task (ie. in screen pinning).
461      */
462     public boolean isLockToAppActive() {
463         try {
464             return ActivityTaskManager.getService().getLockTaskModeState() != LOCK_TASK_MODE_NONE;
465         } catch (RemoteException e) {
466             return false;
467         }
468     }
469 
470     /**
471      * @return whether lock task mode is active in kiosk-mode (not screen pinning).
472      */
473     public boolean isLockTaskKioskModeActive() {
474         try {
475             return ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_LOCKED;
476         } catch (RemoteException e) {
477             return false;
478         }
479     }
480 
481     /**
482      * Shows a voice session identified by {@code token}
483      * @return true if the session was shown, false otherwise
484      */
485     public boolean showVoiceSession(IBinder token, Bundle args, int flags) {
486         IVoiceInteractionManagerService service = IVoiceInteractionManagerService.Stub.asInterface(
487                 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
488         if (service == null) {
489             return false;
490         }
491         try {
492             return service.showSessionFromSession(token, args, flags);
493         } catch (RemoteException e) {
494             return false;
495         }
496     }
497 
498     /**
499      * Returns true if the system supports freeform multi-window.
500      */
501     public boolean supportsFreeformMultiWindow(Context context) {
502         final boolean freeformDevOption = Settings.Global.getInt(context.getContentResolver(),
503                 Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
504         return ActivityTaskManager.supportsMultiWindow(context)
505                 && (context.getPackageManager().hasSystemFeature(
506                 PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
507                 || freeformDevOption);
508     }
509 }
510