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