SystemServicesProxy.java revision ac52f2892d5c72c26387d510093ddfc741a8a632
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 17package com.android.systemui.recents.misc; 18 19import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; 20import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; 21import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; 22import static android.app.ActivityManager.StackId.HOME_STACK_ID; 23import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 24import static android.app.ActivityManager.StackId.PINNED_STACK_ID; 25import static android.app.ActivityManager.StackId.RECENTS_STACK_ID; 26import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT; 27 28import android.annotation.NonNull; 29import android.app.ActivityManager; 30import android.app.ActivityManager.TaskSnapshot; 31import android.app.ActivityOptions; 32import android.app.AppGlobals; 33import android.app.IActivityManager; 34import android.app.KeyguardManager; 35import android.content.ComponentName; 36import android.content.ContentResolver; 37import android.content.Context; 38import android.content.Intent; 39import android.content.pm.ActivityInfo; 40import android.content.pm.ApplicationInfo; 41import android.content.pm.IPackageManager; 42import android.content.pm.PackageManager; 43import android.content.pm.ResolveInfo; 44import android.content.res.Resources; 45import android.graphics.Bitmap; 46import android.graphics.BitmapFactory; 47import android.graphics.Canvas; 48import android.graphics.Color; 49import android.graphics.Paint; 50import android.graphics.Point; 51import android.graphics.PorterDuff; 52import android.graphics.PorterDuffXfermode; 53import android.graphics.Rect; 54import android.graphics.drawable.BitmapDrawable; 55import android.graphics.drawable.ColorDrawable; 56import android.graphics.drawable.Drawable; 57import android.os.Handler; 58import android.os.IRemoteCallback; 59import android.os.Looper; 60import android.os.Message; 61import android.os.ParcelFileDescriptor; 62import android.os.RemoteException; 63import android.os.SystemProperties; 64import android.os.UserHandle; 65import android.os.UserManager; 66import android.provider.Settings; 67import android.util.ArraySet; 68import android.util.LauncherIcons; 69import android.util.Log; 70import android.util.MutableBoolean; 71import android.view.Display; 72import android.view.IAppTransitionAnimationSpecsFuture; 73import android.view.IDockedStackListener; 74import android.view.IWindowManager; 75import android.view.WindowManager; 76import android.view.WindowManager.KeyboardShortcutsReceiver; 77import android.view.WindowManagerGlobal; 78import android.view.accessibility.AccessibilityManager; 79 80import com.android.internal.app.AssistUtils; 81import com.android.internal.os.BackgroundThread; 82import com.android.keyguard.KeyguardUpdateMonitor; 83import com.android.systemui.R; 84import com.android.systemui.pip.tv.PipMenuActivity; 85import com.android.systemui.pip.tv.PipOnboardingActivity; 86import com.android.systemui.recents.Recents; 87import com.android.systemui.recents.RecentsDebugFlags; 88import com.android.systemui.recents.RecentsImpl; 89import com.android.systemui.recents.model.Task; 90import com.android.systemui.recents.model.ThumbnailData; 91 92import java.io.IOException; 93import java.util.ArrayList; 94import java.util.Collections; 95import java.util.Iterator; 96import java.util.List; 97import java.util.Random; 98 99/** 100 * Acts as a shim around the real system services that we need to access data from, and provides 101 * a point of injection when testing UI. 102 */ 103public class SystemServicesProxy { 104 final static String TAG = "SystemServicesProxy"; 105 106 final static BitmapFactory.Options sBitmapOptions; 107 static { 108 sBitmapOptions = new BitmapFactory.Options(); 109 sBitmapOptions.inMutable = true; 110 sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; 111 } 112 113 final static List<String> sRecentsBlacklist; 114 static { 115 sRecentsBlacklist = new ArrayList<>(); 116 sRecentsBlacklist.add(PipOnboardingActivity.class.getName()); 117 sRecentsBlacklist.add(PipMenuActivity.class.getName()); 118 } 119 120 private static SystemServicesProxy sSystemServicesProxy; 121 122 AccessibilityManager mAccm; 123 ActivityManager mAm; 124 IActivityManager mIam; 125 PackageManager mPm; 126 IPackageManager mIpm; 127 AssistUtils mAssistUtils; 128 WindowManager mWm; 129 IWindowManager mIwm; 130 KeyguardManager mKgm; 131 UserManager mUm; 132 Display mDisplay; 133 String mRecentsPackage; 134 ComponentName mAssistComponent; 135 136 boolean mIsSafeMode; 137 boolean mHasFreeformWorkspaceSupport; 138 139 Bitmap mDummyIcon; 140 int mDummyThumbnailWidth; 141 int mDummyThumbnailHeight; 142 Paint mBgProtectionPaint; 143 Canvas mBgProtectionCanvas; 144 LauncherIcons mLauncherIcons; 145 146 private final Handler mHandler = new H(); 147 148 /** 149 * An abstract class to track task stack changes. 150 * Classes should implement this instead of {@link android.app.ITaskStackListener} 151 * to reduce IPC calls from system services. These callbacks will be called on the main thread. 152 */ 153 public abstract static class TaskStackListener { 154 public void onTaskStackChanged() { } 155 public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { } 156 public void onActivityPinned(String packageName) { } 157 public void onActivityUnpinned() { } 158 public void onPinnedActivityRestartAttempt() { } 159 public void onPinnedStackAnimationStarted() { } 160 public void onPinnedStackAnimationEnded() { } 161 public void onActivityForcedResizable(String packageName, int taskId) { } 162 public void onActivityDismissingDockedStack() { } 163 public void onTaskProfileLocked(int taskId, int userId) { } 164 165 /** 166 * Checks that the current user matches the user's SystemUI process. Since 167 * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of 168 * TaskStackListener should make this call to verify that we don't act on events from other 169 * user's processes. 170 */ 171 protected final boolean checkCurrentUserId(boolean debug) { 172 int processUserId = UserHandle.myUserId(); 173 int currentUserId = KeyguardUpdateMonitor.getCurrentUser(); 174 if (processUserId != currentUserId) { 175 if (debug) { 176 Log.d(TAG, "UID mismatch. SystemUI is running uid=" + processUserId 177 + " and the current user is uid=" + currentUserId); 178 } 179 return false; 180 } 181 return true; 182 } 183 } 184 185 /** 186 * Implementation of {@link android.app.ITaskStackListener} to listen task stack changes from 187 * ActivityManagerService. 188 * This simply passes callbacks to listeners through {@link H}. 189 * */ 190 private android.app.TaskStackListener mTaskStackListener = new android.app.TaskStackListener() { 191 @Override 192 public void onTaskStackChanged() throws RemoteException { 193 mHandler.removeMessages(H.ON_TASK_STACK_CHANGED); 194 mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED); 195 } 196 197 @Override 198 public void onActivityPinned(String packageName) throws RemoteException { 199 mHandler.removeMessages(H.ON_ACTIVITY_PINNED); 200 mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, packageName).sendToTarget(); 201 } 202 203 @Override 204 public void onActivityUnpinned() throws RemoteException { 205 mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED); 206 mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED); 207 } 208 209 @Override 210 public void onPinnedActivityRestartAttempt() 211 throws RemoteException{ 212 mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT); 213 mHandler.sendEmptyMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT); 214 } 215 216 @Override 217 public void onPinnedStackAnimationStarted() throws RemoteException { 218 mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED); 219 mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED); 220 } 221 222 @Override 223 public void onPinnedStackAnimationEnded() throws RemoteException { 224 mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED); 225 mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED); 226 } 227 228 @Override 229 public void onActivityForcedResizable(String packageName, int taskId) 230 throws RemoteException { 231 mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, 0, packageName) 232 .sendToTarget(); 233 } 234 235 @Override 236 public void onActivityDismissingDockedStack() throws RemoteException { 237 mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK); 238 } 239 240 @Override 241 public void onTaskProfileLocked(int taskId, int userId) { 242 mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget(); 243 } 244 245 @Override 246 public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) 247 throws RemoteException { 248 mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget(); 249 } 250 }; 251 252 /** 253 * List of {@link TaskStackListener} registered from {@link #registerTaskStackListener}. 254 */ 255 private List<TaskStackListener> mTaskStackListeners = new ArrayList<>(); 256 257 /** Private constructor */ 258 private SystemServicesProxy(Context context) { 259 mAccm = AccessibilityManager.getInstance(context); 260 mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 261 mIam = ActivityManager.getService(); 262 mPm = context.getPackageManager(); 263 mIpm = AppGlobals.getPackageManager(); 264 mAssistUtils = new AssistUtils(context); 265 mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 266 mIwm = WindowManagerGlobal.getWindowManagerService(); 267 mKgm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); 268 mUm = UserManager.get(context); 269 mDisplay = mWm.getDefaultDisplay(); 270 mRecentsPackage = context.getPackageName(); 271 mHasFreeformWorkspaceSupport = 272 mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT) || 273 Settings.Global.getInt(context.getContentResolver(), 274 DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0; 275 mIsSafeMode = mPm.isSafeMode(); 276 277 // Get the dummy thumbnail width/heights 278 Resources res = context.getResources(); 279 int wId = com.android.internal.R.dimen.thumbnail_width; 280 int hId = com.android.internal.R.dimen.thumbnail_height; 281 mDummyThumbnailWidth = res.getDimensionPixelSize(wId); 282 mDummyThumbnailHeight = res.getDimensionPixelSize(hId); 283 284 // Create the protection paints 285 mBgProtectionPaint = new Paint(); 286 mBgProtectionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)); 287 mBgProtectionPaint.setColor(0xFFffffff); 288 mBgProtectionCanvas = new Canvas(); 289 290 // Resolve the assist intent 291 mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.myUserId()); 292 293 if (RecentsDebugFlags.Static.EnableMockTasks) { 294 // Create a dummy icon 295 mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); 296 mDummyIcon.eraseColor(0xFF999999); 297 } 298 299 Collections.addAll(sRecentsBlacklist, 300 res.getStringArray(R.array.recents_blacklist_array)); 301 302 mLauncherIcons = new LauncherIcons(context); 303 } 304 305 /** 306 * Returns the single instance of the {@link SystemServicesProxy}. 307 * This should only be called on the main thread. 308 */ 309 public static SystemServicesProxy getInstance(Context context) { 310 if (!Looper.getMainLooper().isCurrentThread()) { 311 throw new RuntimeException("Must be called on the UI thread"); 312 } 313 if (sSystemServicesProxy == null) { 314 sSystemServicesProxy = new SystemServicesProxy(context); 315 } 316 return sSystemServicesProxy; 317 } 318 319 /** 320 * @return whether the provided {@param className} is blacklisted 321 */ 322 public boolean isBlackListedActivity(String className) { 323 return sRecentsBlacklist.contains(className); 324 } 325 326 /** 327 * Returns a list of the recents tasks. 328 * 329 * @param includeFrontMostExcludedTask if set, will ensure that the front most excluded task 330 * will be visible, otherwise no excluded tasks will be 331 * visible. 332 */ 333 public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId, 334 boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) { 335 if (mAm == null) return null; 336 337 // If we are mocking, then create some recent tasks 338 if (RecentsDebugFlags.Static.EnableMockTasks) { 339 ArrayList<ActivityManager.RecentTaskInfo> tasks = 340 new ArrayList<ActivityManager.RecentTaskInfo>(); 341 int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.MockTaskCount); 342 for (int i = 0; i < count; i++) { 343 // Create a dummy component name 344 int packageIndex = i % RecentsDebugFlags.Static.MockTasksPackageCount; 345 ComponentName cn = new ComponentName("com.android.test" + packageIndex, 346 "com.android.test" + i + ".Activity"); 347 String description = "" + i + " - " + 348 Long.toString(Math.abs(new Random().nextLong()), 36); 349 // Create the recent task info 350 ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); 351 rti.id = rti.persistentId = rti.affiliatedTaskId = i; 352 rti.baseIntent = new Intent(); 353 rti.baseIntent.setComponent(cn); 354 rti.description = description; 355 rti.firstActiveTime = rti.lastActiveTime = i; 356 if (i % 2 == 0) { 357 rti.taskDescription = new ActivityManager.TaskDescription(description, 358 Bitmap.createBitmap(mDummyIcon), null, 359 0xFF000000 | (0xFFFFFF & new Random().nextInt()), 360 0xFF000000 | (0xFFFFFF & new Random().nextInt())); 361 } else { 362 rti.taskDescription = new ActivityManager.TaskDescription(); 363 } 364 tasks.add(rti); 365 } 366 return tasks; 367 } 368 369 // Remove home/recents/excluded tasks 370 int minNumTasksToQuery = 10; 371 int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks); 372 int flags = ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS | 373 ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK | 374 ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS | 375 ActivityManager.RECENT_IGNORE_UNAVAILABLE | 376 ActivityManager.RECENT_INCLUDE_PROFILES; 377 if (includeFrontMostExcludedTask) { 378 flags |= ActivityManager.RECENT_WITH_EXCLUDED; 379 } 380 List<ActivityManager.RecentTaskInfo> tasks = null; 381 try { 382 tasks = mAm.getRecentTasksForUser(numTasksToQuery, flags, userId); 383 } catch (Exception e) { 384 Log.e(TAG, "Failed to get recent tasks", e); 385 } 386 387 // Break early if we can't get a valid set of tasks 388 if (tasks == null) { 389 return new ArrayList<>(); 390 } 391 392 boolean isFirstValidTask = true; 393 Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator(); 394 while (iter.hasNext()) { 395 ActivityManager.RecentTaskInfo t = iter.next(); 396 397 // NOTE: The order of these checks happens in the expected order of the traversal of the 398 // tasks 399 400 // Remove the task if it or it's package are blacklsited 401 if (sRecentsBlacklist.contains(t.realActivity.getClassName()) || 402 sRecentsBlacklist.contains(t.realActivity.getPackageName())) { 403 iter.remove(); 404 continue; 405 } 406 407 // Remove the task if it is marked as excluded, unless it is the first most task and we 408 // are requested to include it 409 boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) 410 == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; 411 isExcluded |= quietProfileIds.contains(t.userId); 412 if (isExcluded && (!isFirstValidTask || !includeFrontMostExcludedTask)) { 413 iter.remove(); 414 } 415 416 isFirstValidTask = false; 417 } 418 419 return tasks.subList(0, Math.min(tasks.size(), numLatestTasks)); 420 } 421 422 /** 423 * Returns the top running task. 424 */ 425 public ActivityManager.RunningTaskInfo getRunningTask() { 426 List<ActivityManager.RunningTaskInfo> tasks = mAm.getRunningTasks(1); 427 if (tasks != null && !tasks.isEmpty()) { 428 return tasks.get(0); 429 } 430 return null; 431 } 432 433 /** 434 * Returns whether the recents activity is currently visible. 435 */ 436 public boolean isRecentsActivityVisible() { 437 return isRecentsActivityVisible(null); 438 } 439 440 /** 441 * Returns whether the recents activity is currently visible. 442 * 443 * @param isHomeStackVisible if provided, will return whether the home stack is visible 444 * regardless of the recents visibility 445 */ 446 public boolean isRecentsActivityVisible(MutableBoolean isHomeStackVisible) { 447 if (mIam == null) return false; 448 449 try { 450 ActivityManager.StackInfo homeStackInfo = mIam.getStackInfo( 451 ActivityManager.StackId.HOME_STACK_ID); 452 ActivityManager.StackInfo fullscreenStackInfo = mIam.getStackInfo( 453 ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID); 454 ActivityManager.StackInfo recentsStackInfo = mIam.getStackInfo( 455 ActivityManager.StackId.RECENTS_STACK_ID); 456 457 boolean homeStackVisibleNotOccluded = isStackNotOccluded(homeStackInfo, 458 fullscreenStackInfo); 459 boolean recentsStackVisibleNotOccluded = isStackNotOccluded(recentsStackInfo, 460 fullscreenStackInfo); 461 if (isHomeStackVisible != null) { 462 isHomeStackVisible.value = homeStackVisibleNotOccluded; 463 } 464 ComponentName topActivity = recentsStackInfo != null ? 465 recentsStackInfo.topActivity : null; 466 return (recentsStackVisibleNotOccluded && topActivity != null 467 && topActivity.getPackageName().equals(RecentsImpl.RECENTS_PACKAGE) 468 && Recents.RECENTS_ACTIVITIES.contains(topActivity.getClassName())); 469 } catch (RemoteException e) { 470 e.printStackTrace(); 471 } 472 return false; 473 } 474 475 private boolean isStackNotOccluded(ActivityManager.StackInfo stackInfo, 476 ActivityManager.StackInfo fullscreenStackInfo) { 477 boolean stackVisibleNotOccluded = stackInfo == null || stackInfo.visible; 478 if (fullscreenStackInfo != null && stackInfo != null) { 479 boolean isFullscreenStackOccludingg = fullscreenStackInfo.visible && 480 fullscreenStackInfo.position > stackInfo.position; 481 stackVisibleNotOccluded &= !isFullscreenStackOccludingg; 482 } 483 return stackVisibleNotOccluded; 484 } 485 486 /** 487 * Returns whether this device has freeform workspaces. 488 */ 489 public boolean hasFreeformWorkspaceSupport() { 490 return mHasFreeformWorkspaceSupport; 491 } 492 493 /** 494 * Returns whether this device is in the safe mode. 495 */ 496 public boolean isInSafeMode() { 497 return mIsSafeMode; 498 } 499 500 /** Docks a task to the side of the screen and starts it. */ 501 public boolean startTaskInDockedMode(int taskId, int createMode) { 502 if (mIam == null) return false; 503 504 try { 505 final ActivityOptions options = ActivityOptions.makeBasic(); 506 options.setDockCreateMode(createMode); 507 options.setLaunchStackId(DOCKED_STACK_ID); 508 mIam.startActivityFromRecents(taskId, options.toBundle()); 509 return true; 510 } catch (Exception e) { 511 Log.e(TAG, "Failed to dock task: " + taskId + " with createMode: " + createMode, e); 512 } 513 return false; 514 } 515 516 /** Docks an already resumed task to the side of the screen. */ 517 public boolean moveTaskToDockedStack(int taskId, int createMode, Rect initialBounds) { 518 if (mIam == null) { 519 return false; 520 } 521 522 try { 523 return mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */, 524 false /* animate */, initialBounds); 525 } catch (RemoteException e) { 526 e.printStackTrace(); 527 } 528 return false; 529 } 530 531 /** 532 * Returns whether the given stack id is the home stack id. 533 */ 534 public static boolean isHomeStack(int stackId) { 535 return stackId == HOME_STACK_ID; 536 } 537 538 /** 539 * Returns whether the given stack id is the pinned stack id. 540 */ 541 public static boolean isPinnedStack(int stackId){ 542 return stackId == PINNED_STACK_ID; 543 } 544 545 /** 546 * Returns whether the given stack id is the docked stack id. 547 */ 548 public static boolean isDockedStack(int stackId) { 549 return stackId == DOCKED_STACK_ID; 550 } 551 552 /** 553 * Returns whether the given stack id is the freeform workspace stack id. 554 */ 555 public static boolean isFreeformStack(int stackId) { 556 return stackId == FREEFORM_WORKSPACE_STACK_ID; 557 } 558 559 /** 560 * @return whether there are any docked tasks for the current user. 561 */ 562 public boolean hasDockedTask() { 563 if (mIam == null) return false; 564 565 ActivityManager.StackInfo stackInfo = null; 566 try { 567 stackInfo = mIam.getStackInfo(DOCKED_STACK_ID); 568 } catch (RemoteException e) { 569 e.printStackTrace(); 570 } 571 572 if (stackInfo != null) { 573 int userId = getCurrentUser(); 574 boolean hasUserTask = false; 575 for (int i = stackInfo.taskUserIds.length - 1; i >= 0 && !hasUserTask; i--) { 576 hasUserTask = (stackInfo.taskUserIds[i] == userId); 577 } 578 return hasUserTask; 579 } 580 return false; 581 } 582 583 /** 584 * Returns whether there is a soft nav bar. 585 */ 586 public boolean hasSoftNavigationBar() { 587 try { 588 return WindowManagerGlobal.getWindowManagerService().hasNavigationBar(); 589 } catch (RemoteException e) { 590 e.printStackTrace(); 591 } 592 return false; 593 } 594 595 /** 596 * Returns whether the device has a transposed nav bar (on the right of the screen) in the 597 * current display orientation. 598 */ 599 public boolean hasTransposedNavigationBar() { 600 Rect insets = new Rect(); 601 getStableInsets(insets); 602 return insets.right > 0; 603 } 604 605 /** 606 * Cancels the current window transtion to/from Recents for the given task id. 607 */ 608 public void cancelWindowTransition(int taskId) { 609 if (mIam == null) return; 610 611 try { 612 mIam.cancelTaskWindowTransition(taskId); 613 } catch (RemoteException e) { 614 e.printStackTrace(); 615 } 616 } 617 618 /** 619 * Cancels the current thumbnail transtion to/from Recents for the given task id. 620 */ 621 public void cancelThumbnailTransition(int taskId) { 622 if (mIam == null) return; 623 624 try { 625 mIam.cancelTaskThumbnailTransition(taskId); 626 } catch (RemoteException e) { 627 e.printStackTrace(); 628 } 629 } 630 631 /** Returns the top task thumbnail for the given task id */ 632 public ThumbnailData getTaskThumbnail(int taskId, boolean reduced) { 633 if (mAm == null) return null; 634 635 // If we are mocking, then just return a dummy thumbnail 636 if (RecentsDebugFlags.Static.EnableMockTasks) { 637 ThumbnailData thumbnailData = new ThumbnailData(); 638 thumbnailData.thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth, 639 mDummyThumbnailHeight, Bitmap.Config.ARGB_8888); 640 thumbnailData.thumbnail.eraseColor(0xff333333); 641 return thumbnailData; 642 } 643 644 ThumbnailData thumbnailData = getThumbnail(taskId, reduced); 645 if (thumbnailData.thumbnail != null && !ActivityManager.ENABLE_TASK_SNAPSHOTS) { 646 thumbnailData.thumbnail.setHasAlpha(false); 647 // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top 648 // left pixel, then assume the whole thumbnail is transparent. Generally, proper 649 // screenshots are always composed onto a bitmap that has no alpha. 650 if (Color.alpha(thumbnailData.thumbnail.getPixel(0, 0)) == 0) { 651 mBgProtectionCanvas.setBitmap(thumbnailData.thumbnail); 652 mBgProtectionCanvas.drawRect(0, 0, thumbnailData.thumbnail.getWidth(), 653 thumbnailData.thumbnail.getHeight(), mBgProtectionPaint); 654 mBgProtectionCanvas.setBitmap(null); 655 Log.e(TAG, "Invalid screenshot detected from getTaskThumbnail()"); 656 } 657 } 658 return thumbnailData; 659 } 660 661 /** 662 * Returns a task thumbnail from the activity manager 663 */ 664 public @NonNull ThumbnailData getThumbnail(int taskId, boolean reducedResolution) { 665 if (mAm == null) { 666 return new ThumbnailData(); 667 } 668 669 final ThumbnailData thumbnailData; 670 if (ActivityManager.ENABLE_TASK_SNAPSHOTS) { 671 ActivityManager.TaskSnapshot snapshot = null; 672 try { 673 snapshot = ActivityManager.getService().getTaskSnapshot(taskId, 674 false /* reducedResolution */); 675 } catch (RemoteException e) { 676 Log.w(TAG, "Failed to retrieve snapshot", e); 677 } 678 if (snapshot != null) { 679 thumbnailData = ThumbnailData.createFromTaskSnapshot(snapshot); 680 } else { 681 return new ThumbnailData(); 682 } 683 } else { 684 ActivityManager.TaskThumbnail taskThumbnail = mAm.getTaskThumbnail(taskId); 685 if (taskThumbnail == null) { 686 return new ThumbnailData(); 687 } 688 689 Bitmap thumbnail = taskThumbnail.mainThumbnail; 690 ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor; 691 if (thumbnail == null && descriptor != null) { 692 thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor(), 693 null, sBitmapOptions); 694 } 695 if (descriptor != null) { 696 try { 697 descriptor.close(); 698 } catch (IOException e) { 699 } 700 } 701 thumbnailData = new ThumbnailData(); 702 thumbnailData.thumbnail = thumbnail; 703 thumbnailData.orientation = taskThumbnail.thumbnailInfo.screenOrientation; 704 thumbnailData.insets.setEmpty(); 705 } 706 return thumbnailData; 707 } 708 709 /** 710 * Moves a task into another stack. 711 */ 712 public void moveTaskToStack(int taskId, int stackId) { 713 if (mIam == null) return; 714 715 try { 716 mIam.positionTaskInStack(taskId, stackId, 0); 717 } catch (RemoteException | IllegalArgumentException e) { 718 e.printStackTrace(); 719 } 720 } 721 722 /** Removes the task */ 723 public void removeTask(final int taskId) { 724 if (mAm == null) return; 725 if (RecentsDebugFlags.Static.EnableMockTasks) return; 726 727 // Remove the task. 728 BackgroundThread.getHandler().post(new Runnable() { 729 @Override 730 public void run() { 731 mAm.removeTask(taskId); 732 } 733 }); 734 } 735 736 /** 737 * Sends a message to close other system windows. 738 */ 739 public void sendCloseSystemWindows(String reason) { 740 try { 741 mIam.closeSystemDialogs(reason); 742 } catch (RemoteException e) { 743 } 744 } 745 746 /** 747 * Returns the activity info for a given component name. 748 * 749 * @param cn The component name of the activity. 750 * @param userId The userId of the user that this is for. 751 */ 752 public ActivityInfo getActivityInfo(ComponentName cn, int userId) { 753 if (mIpm == null) return null; 754 if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo(); 755 756 try { 757 return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId); 758 } catch (RemoteException e) { 759 e.printStackTrace(); 760 return null; 761 } 762 } 763 764 /** 765 * Returns the activity info for a given component name. 766 * 767 * @param cn The component name of the activity. 768 */ 769 public ActivityInfo getActivityInfo(ComponentName cn) { 770 if (mPm == null) return null; 771 if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo(); 772 773 try { 774 return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA); 775 } catch (PackageManager.NameNotFoundException e) { 776 e.printStackTrace(); 777 return null; 778 } 779 } 780 781 /** 782 * Returns the activity label, badging if necessary. 783 */ 784 public String getBadgedActivityLabel(ActivityInfo info, int userId) { 785 if (mPm == null) return null; 786 787 // If we are mocking, then return a mock label 788 if (RecentsDebugFlags.Static.EnableMockTasks) { 789 return "Recent Task: " + userId; 790 } 791 792 return getBadgedLabel(info.loadLabel(mPm).toString(), userId); 793 } 794 795 /** 796 * Returns the application label, badging if necessary. 797 */ 798 public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) { 799 if (mPm == null) return null; 800 801 // If we are mocking, then return a mock label 802 if (RecentsDebugFlags.Static.EnableMockTasks) { 803 return "Recent Task App: " + userId; 804 } 805 806 return getBadgedLabel(appInfo.loadLabel(mPm).toString(), userId); 807 } 808 809 /** 810 * Returns the content description for a given task, badging it if necessary. The content 811 * description joins the app and activity labels. 812 */ 813 public String getBadgedContentDescription(ActivityInfo info, int userId, Resources res) { 814 // If we are mocking, then return a mock label 815 if (RecentsDebugFlags.Static.EnableMockTasks) { 816 return "Recent Task Content Description: " + userId; 817 } 818 819 String activityLabel = info.loadLabel(mPm).toString(); 820 String applicationLabel = info.applicationInfo.loadLabel(mPm).toString(); 821 String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId); 822 return applicationLabel.equals(activityLabel) ? badgedApplicationLabel 823 : res.getString(R.string.accessibility_recents_task_header, 824 badgedApplicationLabel, activityLabel); 825 } 826 827 /** 828 * Returns the activity icon for the ActivityInfo for a user, badging if 829 * necessary. 830 */ 831 public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) { 832 if (mPm == null) return null; 833 834 // If we are mocking, then return a mock label 835 if (RecentsDebugFlags.Static.EnableMockTasks) { 836 return new ColorDrawable(0xFF666666); 837 } 838 839 Drawable icon = mLauncherIcons.wrapIconDrawableWithShadow(info.loadIcon(mPm)); 840 return getBadgedIcon(icon, userId); 841 } 842 843 /** 844 * Returns the application icon for the ApplicationInfo for a user, badging if 845 * necessary. 846 */ 847 public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) { 848 if (mPm == null) return null; 849 850 // If we are mocking, then return a mock label 851 if (RecentsDebugFlags.Static.EnableMockTasks) { 852 return new ColorDrawable(0xFF666666); 853 } 854 855 Drawable icon = mLauncherIcons.wrapIconDrawableWithShadow(appInfo.loadIcon(mPm)); 856 return getBadgedIcon(icon, userId); 857 } 858 859 /** 860 * Returns the task description icon, loading and badging it if it necessary. 861 */ 862 public Drawable getBadgedTaskDescriptionIcon(ActivityManager.TaskDescription taskDescription, 863 int userId, Resources res) { 864 865 // If we are mocking, then return a mock label 866 if (RecentsDebugFlags.Static.EnableMockTasks) { 867 return new ColorDrawable(0xFF666666); 868 } 869 870 Bitmap tdIcon = taskDescription.getInMemoryIcon(); 871 if (tdIcon == null) { 872 tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon( 873 taskDescription.getIconFilename(), userId); 874 } 875 if (tdIcon != null) { 876 return getBadgedIcon(new BitmapDrawable(res, tdIcon), userId); 877 } 878 return null; 879 } 880 881 public ActivityManager.TaskDescription getTaskDescription(int taskId) { 882 try { 883 return mIam.getTaskDescription(taskId); 884 } catch (RemoteException e) { 885 return null; 886 } 887 } 888 889 /** 890 * Returns the given icon for a user, badging if necessary. 891 */ 892 private Drawable getBadgedIcon(Drawable icon, int userId) { 893 if (userId != UserHandle.myUserId()) { 894 icon = mPm.getUserBadgedIcon(icon, new UserHandle(userId)); 895 } 896 return icon; 897 } 898 899 /** 900 * Returns a banner used on TV for the specified Activity. 901 */ 902 public Drawable getActivityBanner(ActivityInfo info) { 903 if (mPm == null) return null; 904 905 // If we are mocking, then return a mock banner 906 if (RecentsDebugFlags.Static.EnableMockTasks) { 907 return new ColorDrawable(0xFF666666); 908 } 909 910 Drawable banner = info.loadBanner(mPm); 911 return banner; 912 } 913 914 /** 915 * Returns a logo used on TV for the specified Activity. 916 */ 917 public Drawable getActivityLogo(ActivityInfo info) { 918 if (mPm == null) return null; 919 920 // If we are mocking, then return a mock logo 921 if (RecentsDebugFlags.Static.EnableMockTasks) { 922 return new ColorDrawable(0xFF666666); 923 } 924 925 Drawable logo = info.loadLogo(mPm); 926 return logo; 927 } 928 929 930 /** 931 * Returns the given label for a user, badging if necessary. 932 */ 933 private String getBadgedLabel(String label, int userId) { 934 if (userId != UserHandle.myUserId()) { 935 label = mPm.getUserBadgedLabel(label, new UserHandle(userId)).toString(); 936 } 937 return label; 938 } 939 940 /** 941 * Returns whether the provided {@param userId} is currently locked (and showing Keyguard). 942 */ 943 public boolean isDeviceLocked(int userId) { 944 if (mKgm == null) { 945 return false; 946 } 947 return mKgm.isDeviceLocked(userId); 948 } 949 950 /** Returns the package name of the home activity. */ 951 public String getHomeActivityPackageName() { 952 if (mPm == null) return null; 953 if (RecentsDebugFlags.Static.EnableMockTasks) return null; 954 955 ArrayList<ResolveInfo> homeActivities = new ArrayList<>(); 956 ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities); 957 if (defaultHomeActivity != null) { 958 return defaultHomeActivity.getPackageName(); 959 } else if (homeActivities.size() == 1) { 960 ResolveInfo info = homeActivities.get(0); 961 if (info.activityInfo != null) { 962 return info.activityInfo.packageName; 963 } 964 } 965 return null; 966 } 967 968 /** 969 * Returns whether the provided {@param userId} represents the system user. 970 */ 971 public boolean isSystemUser(int userId) { 972 return userId == UserHandle.USER_SYSTEM; 973 } 974 975 /** 976 * Returns the current user id. 977 */ 978 public int getCurrentUser() { 979 if (mAm == null) return 0; 980 981 return mAm.getCurrentUser(); 982 } 983 984 /** 985 * Returns the processes user id. 986 */ 987 public int getProcessUser() { 988 if (mUm == null) return 0; 989 return mUm.getUserHandle(); 990 } 991 992 /** 993 * Returns whether touch exploration is currently enabled. 994 */ 995 public boolean isTouchExplorationEnabled() { 996 if (mAccm == null) return false; 997 998 return mAccm.isEnabled() && mAccm.isTouchExplorationEnabled(); 999 } 1000 1001 /** 1002 * Returns whether the current task is in screen-pinning mode. 1003 */ 1004 public boolean isScreenPinningActive() { 1005 if (mIam == null) return false; 1006 1007 try { 1008 return mIam.isInLockTaskMode(); 1009 } catch (RemoteException e) { 1010 return false; 1011 } 1012 } 1013 1014 /** 1015 * Returns a global setting. 1016 */ 1017 public int getGlobalSetting(Context context, String setting) { 1018 ContentResolver cr = context.getContentResolver(); 1019 return Settings.Global.getInt(cr, setting, 0); 1020 } 1021 1022 /** 1023 * Returns a system setting. 1024 */ 1025 public int getSystemSetting(Context context, String setting) { 1026 ContentResolver cr = context.getContentResolver(); 1027 return Settings.System.getInt(cr, setting, 0); 1028 } 1029 1030 /** 1031 * Returns a system property. 1032 */ 1033 public String getSystemProperty(String key) { 1034 return SystemProperties.get(key); 1035 } 1036 1037 /** 1038 * Returns the smallest width/height. 1039 */ 1040 public int getDeviceSmallestWidth() { 1041 if (mDisplay == null) return 0; 1042 1043 Point smallestSizeRange = new Point(); 1044 Point largestSizeRange = new Point(); 1045 mDisplay.getCurrentSizeRange(smallestSizeRange, largestSizeRange); 1046 return smallestSizeRange.x; 1047 } 1048 1049 /** 1050 * Returns the current display rect in the current display orientation. 1051 */ 1052 public Rect getDisplayRect() { 1053 Rect displayRect = new Rect(); 1054 if (mDisplay == null) return displayRect; 1055 1056 Point p = new Point(); 1057 mDisplay.getRealSize(p); 1058 displayRect.set(0, 0, p.x, p.y); 1059 return displayRect; 1060 } 1061 1062 /** 1063 * Returns the window rect for the RecentsActivity, based on the dimensions of the recents stack 1064 */ 1065 public Rect getWindowRect() { 1066 Rect windowRect = new Rect(); 1067 if (mIam == null) return windowRect; 1068 1069 try { 1070 // Use the recents stack bounds, fallback to fullscreen stack if it is null 1071 ActivityManager.StackInfo stackInfo = mIam.getStackInfo(RECENTS_STACK_ID); 1072 if (stackInfo == null) { 1073 stackInfo = mIam.getStackInfo(FULLSCREEN_WORKSPACE_STACK_ID); 1074 } 1075 if (stackInfo != null) { 1076 windowRect.set(stackInfo.bounds); 1077 } 1078 } catch (RemoteException e) { 1079 e.printStackTrace(); 1080 } finally { 1081 return windowRect; 1082 } 1083 } 1084 1085 /** Starts an activity from recents. */ 1086 public boolean startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName, 1087 ActivityOptions options, int stackId) { 1088 if (mIam != null) { 1089 try { 1090 if (taskKey.stackId == DOCKED_STACK_ID) { 1091 // We show non-visible docked tasks in Recents, but we always want to launch 1092 // them in the fullscreen stack. 1093 if (options == null) { 1094 options = ActivityOptions.makeBasic(); 1095 } 1096 options.setLaunchStackId(FULLSCREEN_WORKSPACE_STACK_ID); 1097 } else if (stackId != INVALID_STACK_ID){ 1098 if (options == null) { 1099 options = ActivityOptions.makeBasic(); 1100 } 1101 options.setLaunchStackId(stackId); 1102 } 1103 mIam.startActivityFromRecents( 1104 taskKey.id, options == null ? null : options.toBundle()); 1105 return true; 1106 } catch (Exception e) { 1107 Log.e(TAG, context.getString(R.string.recents_launch_error_message, taskName), e); 1108 } 1109 } 1110 return false; 1111 } 1112 1113 /** Starts an in-place animation on the front most application windows. */ 1114 public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) { 1115 if (mIam == null) return; 1116 1117 try { 1118 mIam.startInPlaceAnimationOnFrontMostApplication( 1119 opts == null ? null : opts.toBundle()); 1120 } catch (Exception e) { 1121 e.printStackTrace(); 1122 } 1123 } 1124 1125 /** 1126 * Registers a task stack listener with the system. 1127 * This should be called on the main thread. 1128 */ 1129 public void registerTaskStackListener(TaskStackListener listener) { 1130 if (mIam == null) return; 1131 1132 mTaskStackListeners.add(listener); 1133 if (mTaskStackListeners.size() == 1) { 1134 // Register mTaskStackListener to IActivityManager only once if needed. 1135 try { 1136 mIam.registerTaskStackListener(mTaskStackListener); 1137 } catch (Exception e) { 1138 Log.w(TAG, "Failed to call registerTaskStackListener", e); 1139 } 1140 } 1141 } 1142 1143 public void endProlongedAnimations() { 1144 if (mWm == null) { 1145 return; 1146 } 1147 try { 1148 WindowManagerGlobal.getWindowManagerService().endProlongedAnimations(); 1149 } catch (Exception e) { 1150 e.printStackTrace(); 1151 } 1152 } 1153 1154 public void registerDockedStackListener(IDockedStackListener listener) { 1155 if (mWm == null) return; 1156 1157 try { 1158 WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(listener); 1159 } catch (Exception e) { 1160 e.printStackTrace(); 1161 } 1162 } 1163 1164 /** 1165 * Calculates the size of the dock divider in the current orientation. 1166 */ 1167 public int getDockedDividerSize(Context context) { 1168 Resources res = context.getResources(); 1169 int dividerWindowWidth = res.getDimensionPixelSize( 1170 com.android.internal.R.dimen.docked_stack_divider_thickness); 1171 int dividerInsets = res.getDimensionPixelSize( 1172 com.android.internal.R.dimen.docked_stack_divider_insets); 1173 return dividerWindowWidth - 2 * dividerInsets; 1174 } 1175 1176 public void requestKeyboardShortcuts( 1177 Context context, KeyboardShortcutsReceiver receiver, int deviceId) { 1178 mWm.requestAppKeyboardShortcuts(receiver, deviceId); 1179 } 1180 1181 public void getStableInsets(Rect outStableInsets) { 1182 if (mWm == null) return; 1183 1184 try { 1185 WindowManagerGlobal.getWindowManagerService().getStableInsets(Display.DEFAULT_DISPLAY, 1186 outStableInsets); 1187 } catch (Exception e) { 1188 e.printStackTrace(); 1189 } 1190 } 1191 1192 public void overridePendingAppTransitionMultiThumbFuture( 1193 IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener, 1194 boolean scaleUp) { 1195 try { 1196 WindowManagerGlobal.getWindowManagerService() 1197 .overridePendingAppTransitionMultiThumbFuture(future, animStartedListener, 1198 scaleUp); 1199 } catch (RemoteException e) { 1200 Log.w(TAG, "Failed to override transition: " + e); 1201 } 1202 } 1203 1204 /** 1205 * Updates the visibility of recents. 1206 */ 1207 public void setRecentsVisibility(boolean visible) { 1208 try { 1209 mIwm.setRecentsVisibility(visible); 1210 } catch (RemoteException e) { 1211 Log.e(TAG, "Unable to reach window manager", e); 1212 } 1213 } 1214 1215 /** 1216 * Updates the visibility of the picture-in-picture. 1217 */ 1218 public void setPipVisibility(boolean visible) { 1219 try { 1220 mIwm.setPipVisibility(visible); 1221 } catch (RemoteException e) { 1222 Log.e(TAG, "Unable to reach window manager", e); 1223 } 1224 } 1225 1226 private final class H extends Handler { 1227 private static final int ON_TASK_STACK_CHANGED = 1; 1228 private static final int ON_TASK_SNAPSHOT_CHANGED = 2; 1229 private static final int ON_ACTIVITY_PINNED = 3; 1230 private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 4; 1231 private static final int ON_PINNED_STACK_ANIMATION_ENDED = 5; 1232 private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6; 1233 private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7; 1234 private static final int ON_TASK_PROFILE_LOCKED = 8; 1235 private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9; 1236 private static final int ON_ACTIVITY_UNPINNED = 10; 1237 1238 @Override 1239 public void handleMessage(Message msg) { 1240 switch (msg.what) { 1241 case ON_TASK_STACK_CHANGED: { 1242 for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { 1243 mTaskStackListeners.get(i).onTaskStackChanged(); 1244 } 1245 break; 1246 } 1247 case ON_TASK_SNAPSHOT_CHANGED: { 1248 for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { 1249 mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1, 1250 (TaskSnapshot) msg.obj); 1251 } 1252 break; 1253 } 1254 case ON_ACTIVITY_PINNED: { 1255 for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { 1256 mTaskStackListeners.get(i).onActivityPinned((String) msg.obj); 1257 } 1258 break; 1259 } 1260 case ON_ACTIVITY_UNPINNED: { 1261 for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { 1262 mTaskStackListeners.get(i).onActivityUnpinned(); 1263 } 1264 break; 1265 } 1266 case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: { 1267 for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { 1268 mTaskStackListeners.get(i).onPinnedActivityRestartAttempt(); 1269 } 1270 break; 1271 } 1272 case ON_PINNED_STACK_ANIMATION_STARTED: { 1273 for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { 1274 mTaskStackListeners.get(i).onPinnedStackAnimationStarted(); 1275 } 1276 break; 1277 } 1278 case ON_PINNED_STACK_ANIMATION_ENDED: { 1279 for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { 1280 mTaskStackListeners.get(i).onPinnedStackAnimationEnded(); 1281 } 1282 break; 1283 } 1284 case ON_ACTIVITY_FORCED_RESIZABLE: { 1285 for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { 1286 mTaskStackListeners.get(i).onActivityForcedResizable( 1287 (String) msg.obj, msg.arg1); 1288 } 1289 break; 1290 } 1291 case ON_ACTIVITY_DISMISSING_DOCKED_STACK: { 1292 for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { 1293 mTaskStackListeners.get(i).onActivityDismissingDockedStack(); 1294 } 1295 break; 1296 } 1297 case ON_TASK_PROFILE_LOCKED: { 1298 for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { 1299 mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2); 1300 } 1301 break; 1302 } 1303 } 1304 } 1305 } 1306} 1307