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