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