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