1 /* 2 * Copyright (C) 2014 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.recents.misc; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; 23 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 24 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; 25 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; 26 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 27 28 import android.app.ActivityManager; 29 import android.app.ActivityManager.StackInfo; 30 import android.app.ActivityOptions; 31 import android.app.ActivityTaskManager; 32 import android.app.AppGlobals; 33 import android.app.IActivityManager; 34 import android.app.IActivityTaskManager; 35 import android.app.WindowConfiguration; 36 import android.content.ComponentName; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.content.pm.IPackageManager; 40 import android.content.pm.PackageManager; 41 import android.content.res.Resources; 42 import android.graphics.Bitmap; 43 import android.graphics.BitmapFactory; 44 import android.graphics.Canvas; 45 import android.graphics.Paint; 46 import android.graphics.Point; 47 import android.graphics.PorterDuff; 48 import android.graphics.PorterDuffXfermode; 49 import android.graphics.Rect; 50 import android.graphics.drawable.Drawable; 51 import android.os.RemoteException; 52 import android.os.ServiceManager; 53 import android.os.UserHandle; 54 import android.os.UserManager; 55 import android.service.dreams.DreamService; 56 import android.service.dreams.IDreamManager; 57 import android.util.Log; 58 import android.util.MutableBoolean; 59 import android.view.Display; 60 import android.view.IDockedStackListener; 61 import android.view.IWindowManager; 62 import android.view.WindowManager; 63 import android.view.WindowManager.KeyboardShortcutsReceiver; 64 import android.view.WindowManagerGlobal; 65 import android.view.accessibility.AccessibilityManager; 66 67 import com.android.internal.app.AssistUtils; 68 import com.android.internal.os.BackgroundThread; 69 import com.android.systemui.Dependency; 70 import com.android.systemui.UiOffloadThread; 71 import com.android.systemui.recents.LegacyRecentsImpl; 72 import com.android.systemui.recents.RecentsImpl; 73 import com.android.systemui.statusbar.policy.UserInfoController; 74 75 import java.util.List; 76 77 /** 78 * Acts as a shim around the real system services that we need to access data from, and provides 79 * a point of injection when testing UI. 80 */ 81 public class SystemServicesProxy { 82 final static String TAG = "SystemServicesProxy"; 83 84 final static BitmapFactory.Options sBitmapOptions; 85 static { 86 sBitmapOptions = new BitmapFactory.Options(); 87 sBitmapOptions.inMutable = true; 88 sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; 89 } 90 91 private static SystemServicesProxy sSystemServicesProxy; 92 93 AccessibilityManager mAccm; 94 ActivityManager mAm; 95 IActivityManager mIam; 96 IActivityTaskManager mIatm; 97 PackageManager mPm; 98 IPackageManager mIpm; 99 private final IDreamManager mDreamManager; 100 private final Context mContext; 101 AssistUtils mAssistUtils; 102 WindowManager mWm; 103 IWindowManager mIwm; 104 UserManager mUm; 105 Display mDisplay; 106 String mRecentsPackage; 107 private int mCurrentUserId; 108 109 boolean mIsSafeMode; 110 111 int mDummyThumbnailWidth; 112 int mDummyThumbnailHeight; 113 Paint mBgProtectionPaint; 114 Canvas mBgProtectionCanvas; 115 116 private final Runnable mGcRunnable = new Runnable() { 117 @Override 118 public void run() { 119 System.gc(); 120 System.runFinalization(); 121 } 122 }; 123 124 private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class); 125 126 private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener = 127 (String name, Drawable picture, String userAccount) -> 128 mCurrentUserId = mAm.getCurrentUser(); 129 130 /** Private constructor */ SystemServicesProxy(Context context)131 private SystemServicesProxy(Context context) { 132 mContext = context.getApplicationContext(); 133 mAccm = AccessibilityManager.getInstance(context); 134 mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 135 mIam = ActivityManager.getService(); 136 mIatm = ActivityTaskManager.getService(); 137 mPm = context.getPackageManager(); 138 mIpm = AppGlobals.getPackageManager(); 139 mAssistUtils = new AssistUtils(context); 140 mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 141 mIwm = WindowManagerGlobal.getWindowManagerService(); 142 mUm = UserManager.get(context); 143 mDreamManager = IDreamManager.Stub.asInterface( 144 ServiceManager.checkService(DreamService.DREAM_SERVICE)); 145 mDisplay = mWm.getDefaultDisplay(); 146 mRecentsPackage = context.getPackageName(); 147 mIsSafeMode = mPm.isSafeMode(); 148 mCurrentUserId = mAm.getCurrentUser(); 149 150 // Get the dummy thumbnail width/heights 151 Resources res = context.getResources(); 152 int wId = com.android.internal.R.dimen.thumbnail_width; 153 int hId = com.android.internal.R.dimen.thumbnail_height; 154 mDummyThumbnailWidth = res.getDimensionPixelSize(wId); 155 mDummyThumbnailHeight = res.getDimensionPixelSize(hId); 156 157 // Create the protection paints 158 mBgProtectionPaint = new Paint(); 159 mBgProtectionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)); 160 mBgProtectionPaint.setColor(0xFFffffff); 161 mBgProtectionCanvas = new Canvas(); 162 163 // Since SystemServicesProxy can be accessed from a per-SysUI process component, create a 164 // per-process listener to keep track of the current user id to reduce the number of binder 165 // calls to fetch it. 166 UserInfoController userInfoController = Dependency.get(UserInfoController.class); 167 userInfoController.addCallback(mOnUserInfoChangedListener); 168 } 169 170 /** 171 * Returns the single instance of the {@link SystemServicesProxy}. 172 * This should only be called on the main thread. 173 */ getInstance(Context context)174 public static synchronized SystemServicesProxy getInstance(Context context) { 175 if (sSystemServicesProxy == null) { 176 sSystemServicesProxy = new SystemServicesProxy(context); 177 } 178 return sSystemServicesProxy; 179 } 180 181 /** 182 * Requests a gc() from the background thread. 183 */ gc()184 public void gc() { 185 BackgroundThread.getHandler().post(mGcRunnable); 186 } 187 188 /** 189 * Returns whether the recents activity is currently visible. 190 */ isRecentsActivityVisible()191 public boolean isRecentsActivityVisible() { 192 return isRecentsActivityVisible(null); 193 } 194 195 /** 196 * Returns whether the recents activity is currently visible. 197 * 198 * @param isHomeStackVisible if provided, will return whether the home stack is visible 199 * regardless of the recents visibility 200 * 201 * TODO(winsonc): Refactor this check to just use the recents activity lifecycle 202 */ isRecentsActivityVisible(MutableBoolean isHomeStackVisible)203 public boolean isRecentsActivityVisible(MutableBoolean isHomeStackVisible) { 204 if (mIam == null) return false; 205 206 try { 207 List<StackInfo> stackInfos = mIatm.getAllStackInfos(); 208 ActivityManager.StackInfo homeStackInfo = null; 209 ActivityManager.StackInfo fullscreenStackInfo = null; 210 ActivityManager.StackInfo recentsStackInfo = null; 211 for (int i = 0; i < stackInfos.size(); i++) { 212 final StackInfo stackInfo = stackInfos.get(i); 213 final WindowConfiguration winConfig = stackInfo.configuration.windowConfiguration; 214 final int activityType = winConfig.getActivityType(); 215 final int windowingMode = winConfig.getWindowingMode(); 216 if (homeStackInfo == null && activityType == ACTIVITY_TYPE_HOME) { 217 homeStackInfo = stackInfo; 218 } else if (fullscreenStackInfo == null && activityType == ACTIVITY_TYPE_STANDARD 219 && (windowingMode == WINDOWING_MODE_FULLSCREEN 220 || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) { 221 fullscreenStackInfo = stackInfo; 222 } else if (recentsStackInfo == null && activityType == ACTIVITY_TYPE_RECENTS) { 223 recentsStackInfo = stackInfo; 224 } 225 } 226 boolean homeStackVisibleNotOccluded = isStackNotOccluded(homeStackInfo, 227 fullscreenStackInfo); 228 boolean recentsStackVisibleNotOccluded = isStackNotOccluded(recentsStackInfo, 229 fullscreenStackInfo); 230 if (isHomeStackVisible != null) { 231 isHomeStackVisible.value = homeStackVisibleNotOccluded; 232 } 233 ComponentName topActivity = recentsStackInfo != null ? 234 recentsStackInfo.topActivity : null; 235 return (recentsStackVisibleNotOccluded && topActivity != null 236 && topActivity.getPackageName().equals(RecentsImpl.RECENTS_PACKAGE) 237 && LegacyRecentsImpl.RECENTS_ACTIVITIES.contains(topActivity.getClassName())); 238 } catch (RemoteException e) { 239 e.printStackTrace(); 240 } 241 return false; 242 } 243 isStackNotOccluded(ActivityManager.StackInfo stackInfo, ActivityManager.StackInfo fullscreenStackInfo)244 private boolean isStackNotOccluded(ActivityManager.StackInfo stackInfo, 245 ActivityManager.StackInfo fullscreenStackInfo) { 246 boolean stackVisibleNotOccluded = stackInfo == null || stackInfo.visible; 247 if (fullscreenStackInfo != null && stackInfo != null) { 248 boolean isFullscreenStackOccludingg = fullscreenStackInfo.visible && 249 fullscreenStackInfo.position > stackInfo.position; 250 stackVisibleNotOccluded &= !isFullscreenStackOccludingg; 251 } 252 return stackVisibleNotOccluded; 253 } 254 255 /** 256 * Returns whether this device is in the safe mode. 257 */ isInSafeMode()258 public boolean isInSafeMode() { 259 return mIsSafeMode; 260 } 261 262 /** Moves an already resumed task to the side of the screen to initiate split screen. */ setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, Rect initialBounds)263 public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, 264 Rect initialBounds) { 265 if (mIatm == null) { 266 return false; 267 } 268 269 try { 270 return mIatm.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, 271 true /* onTop */, false /* animate */, initialBounds, true /* showRecents */); 272 } catch (RemoteException e) { 273 e.printStackTrace(); 274 } 275 return false; 276 } 277 getSplitScreenPrimaryStack()278 public ActivityManager.StackInfo getSplitScreenPrimaryStack() { 279 try { 280 return mIatm.getStackInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED); 281 } catch (RemoteException e) { 282 return null; 283 } 284 } 285 286 /** 287 * @return whether there are any docked tasks for the current user. 288 */ hasDockedTask()289 public boolean hasDockedTask() { 290 if (mIam == null) return false; 291 292 ActivityManager.StackInfo stackInfo = getSplitScreenPrimaryStack(); 293 if (stackInfo != null) { 294 int userId = getCurrentUser(); 295 boolean hasUserTask = false; 296 for (int i = stackInfo.taskUserIds.length - 1; i >= 0 && !hasUserTask; i--) { 297 hasUserTask = (stackInfo.taskUserIds[i] == userId); 298 } 299 return hasUserTask; 300 } 301 return false; 302 } 303 304 /** 305 * Returns whether there is a soft nav bar on specified display. 306 * 307 * @param displayId the id of display to check if there is a software navigation bar. 308 */ hasSoftNavigationBar(int displayId)309 public boolean hasSoftNavigationBar(int displayId) { 310 try { 311 return mIwm.hasNavigationBar(displayId); 312 } catch (RemoteException e) { 313 e.printStackTrace(); 314 } 315 return false; 316 } 317 318 /** 319 * Returns whether the device has a transposed nav bar (on the right of the screen) in the 320 * current display orientation. 321 */ hasTransposedNavigationBar()322 public boolean hasTransposedNavigationBar() { 323 Rect insets = new Rect(); 324 getStableInsets(insets); 325 return insets.right > 0; 326 } 327 328 /** Set the task's windowing mode. */ setTaskWindowingMode(int taskId, int windowingMode)329 public void setTaskWindowingMode(int taskId, int windowingMode) { 330 if (mIatm == null) return; 331 332 try { 333 mIatm.setTaskWindowingMode(taskId, windowingMode, false /* onTop */); 334 } catch (RemoteException | IllegalArgumentException e) { 335 e.printStackTrace(); 336 } 337 } 338 339 /** 340 * Returns whether the provided {@param userId} represents the system user. 341 */ isSystemUser(int userId)342 public boolean isSystemUser(int userId) { 343 return userId == UserHandle.USER_SYSTEM; 344 } 345 346 /** 347 * Returns the current user id. Used instead of KeyguardUpdateMonitor in SystemUI components 348 * that run in the non-primary SystemUI process. 349 */ getCurrentUser()350 public int getCurrentUser() { 351 return mCurrentUserId; 352 } 353 354 /** 355 * Returns the processes user id. 356 */ getProcessUser()357 public int getProcessUser() { 358 if (mUm == null) return 0; 359 return mUm.getUserHandle(); 360 } 361 362 /** 363 * Returns whether touch exploration is currently enabled. 364 */ isTouchExplorationEnabled()365 public boolean isTouchExplorationEnabled() { 366 if (mAccm == null) return false; 367 368 return mAccm.isEnabled() && mAccm.isTouchExplorationEnabled(); 369 } 370 371 /** 372 * Returns whether the current task is in screen-pinning mode. 373 */ isScreenPinningActive()374 public boolean isScreenPinningActive() { 375 if (mIam == null) return false; 376 377 try { 378 return mIatm.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED; 379 } catch (RemoteException e) { 380 return false; 381 } 382 } 383 384 /** 385 * Returns the smallest width/height. 386 */ getDeviceSmallestWidth()387 public int getDeviceSmallestWidth() { 388 if (mDisplay == null) return 0; 389 390 Point smallestSizeRange = new Point(); 391 Point largestSizeRange = new Point(); 392 mDisplay.getCurrentSizeRange(smallestSizeRange, largestSizeRange); 393 return smallestSizeRange.x; 394 } 395 396 /** 397 * Returns the current display rect in the current display orientation. 398 */ getDisplayRect()399 public Rect getDisplayRect() { 400 Rect displayRect = new Rect(); 401 if (mDisplay == null) return displayRect; 402 403 Point p = new Point(); 404 mDisplay.getRealSize(p); 405 displayRect.set(0, 0, p.x, p.y); 406 return displayRect; 407 } 408 409 /** 410 * Returns the window rect for the RecentsActivity, based on the dimensions of the recents stack 411 */ getWindowRect()412 public Rect getWindowRect() { 413 Rect windowRect = new Rect(); 414 if (mIam == null) return windowRect; 415 416 try { 417 // Use the recents stack bounds, fallback to fullscreen stack if it is null 418 ActivityManager.StackInfo stackInfo = 419 mIatm.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS); 420 if (stackInfo == null) { 421 stackInfo = mIatm.getStackInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); 422 } 423 if (stackInfo != null) { 424 windowRect.set(stackInfo.bounds); 425 } 426 } catch (RemoteException e) { 427 e.printStackTrace(); 428 } finally { 429 return windowRect; 430 } 431 } 432 startActivityAsUserAsync(Intent intent, ActivityOptions opts)433 public void startActivityAsUserAsync(Intent intent, ActivityOptions opts) { 434 mUiOffloadThread.submit(() -> mContext.startActivityAsUser(intent, 435 opts != null ? opts.toBundle() : null, UserHandle.CURRENT)); 436 } 437 438 /** Starts an in-place animation on the front most application windows. */ startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts)439 public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) { 440 if (mIam == null) return; 441 442 try { 443 mIatm.startInPlaceAnimationOnFrontMostApplication( 444 opts == null ? null : opts.toBundle()); 445 } catch (Exception e) { 446 e.printStackTrace(); 447 } 448 } 449 registerDockedStackListener(IDockedStackListener listener)450 public void registerDockedStackListener(IDockedStackListener listener) { 451 if (mWm == null) return; 452 453 try { 454 mIwm.registerDockedStackListener(listener); 455 } catch (Exception e) { 456 e.printStackTrace(); 457 } 458 } 459 460 /** 461 * Calculates the size of the dock divider in the current orientation. 462 */ getDockedDividerSize(Context context)463 public int getDockedDividerSize(Context context) { 464 Resources res = context.getResources(); 465 int dividerWindowWidth = res.getDimensionPixelSize( 466 com.android.internal.R.dimen.docked_stack_divider_thickness); 467 int dividerInsets = res.getDimensionPixelSize( 468 com.android.internal.R.dimen.docked_stack_divider_insets); 469 return dividerWindowWidth - 2 * dividerInsets; 470 } 471 requestKeyboardShortcuts( Context context, KeyboardShortcutsReceiver receiver, int deviceId)472 public void requestKeyboardShortcuts( 473 Context context, KeyboardShortcutsReceiver receiver, int deviceId) { 474 mWm.requestAppKeyboardShortcuts(receiver, deviceId); 475 } 476 getStableInsets(Rect outStableInsets)477 public void getStableInsets(Rect outStableInsets) { 478 if (mWm == null) return; 479 480 try { 481 mIwm.getStableInsets(Display.DEFAULT_DISPLAY, outStableInsets); 482 } catch (Exception e) { 483 e.printStackTrace(); 484 } 485 } 486 487 /** 488 * Updates the visibility of recents. 489 */ setRecentsVisibility(final boolean visible)490 public void setRecentsVisibility(final boolean visible) { 491 mUiOffloadThread.submit(() -> { 492 try { 493 mIwm.setRecentsVisibility(visible); 494 } catch (RemoteException e) { 495 Log.e(TAG, "Unable to reach window manager", e); 496 } 497 }); 498 } 499 500 /** 501 * Updates the visibility of the picture-in-picture. 502 */ setPipVisibility(final boolean visible)503 public void setPipVisibility(final boolean visible) { 504 mUiOffloadThread.submit(() -> { 505 try { 506 mIwm.setPipVisibility(visible); 507 } catch (RemoteException e) { 508 Log.e(TAG, "Unable to reach window manager", e); 509 } 510 }); 511 } 512 isDreaming()513 public boolean isDreaming() { 514 try { 515 return mDreamManager.isDreaming(); 516 } catch (RemoteException e) { 517 Log.e(TAG, "Failed to query dream manager.", e); 518 } 519 return false; 520 } 521 awakenDreamsAsync()522 public void awakenDreamsAsync() { 523 mUiOffloadThread.submit(() -> { 524 try { 525 mDreamManager.awaken(); 526 } catch (RemoteException e) { 527 e.printStackTrace(); 528 } 529 }); 530 } 531 532 public interface StartActivityFromRecentsResultListener { onStartActivityResult(boolean succeeded)533 void onStartActivityResult(boolean succeeded); 534 } 535 } 536