1 /* 2 * Copyright (C) 2020 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.wm.shell.startingsurface; 18 19 import static android.content.Context.CONTEXT_RESTRICTED; 20 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; 21 import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION; 22 import static android.view.Display.DEFAULT_DISPLAY; 23 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN; 24 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT; 25 26 import android.annotation.Nullable; 27 import android.app.ActivityManager.RunningTaskInfo; 28 import android.app.ActivityTaskManager; 29 import android.app.ActivityThread; 30 import android.app.TaskInfo; 31 import android.content.Context; 32 import android.content.pm.ActivityInfo; 33 import android.content.pm.IPackageManager; 34 import android.content.pm.PackageManager; 35 import android.content.res.Configuration; 36 import android.content.res.Resources; 37 import android.content.res.TypedArray; 38 import android.graphics.Color; 39 import android.graphics.PixelFormat; 40 import android.hardware.display.DisplayManager; 41 import android.os.IBinder; 42 import android.os.RemoteCallback; 43 import android.os.RemoteException; 44 import android.os.SystemClock; 45 import android.os.Trace; 46 import android.os.UserHandle; 47 import android.util.Slog; 48 import android.util.SparseArray; 49 import android.view.Choreographer; 50 import android.view.Display; 51 import android.view.SurfaceControlViewHost; 52 import android.view.View; 53 import android.view.WindowInsetsController; 54 import android.view.WindowManager; 55 import android.view.WindowManagerGlobal; 56 import android.widget.FrameLayout; 57 import android.window.SplashScreenView; 58 import android.window.SplashScreenView.SplashScreenViewParcelable; 59 import android.window.StartingWindowInfo; 60 import android.window.StartingWindowInfo.StartingWindowType; 61 import android.window.StartingWindowRemovalInfo; 62 import android.window.TaskSnapshot; 63 64 import com.android.internal.R; 65 import com.android.internal.annotations.VisibleForTesting; 66 import com.android.internal.protolog.common.ProtoLog; 67 import com.android.internal.util.ContrastColorUtil; 68 import com.android.launcher3.icons.IconProvider; 69 import com.android.wm.shell.common.ShellExecutor; 70 import com.android.wm.shell.common.TransactionPool; 71 import com.android.wm.shell.common.annotations.ShellSplashscreenThread; 72 import com.android.wm.shell.protolog.ShellProtoLogGroup; 73 74 import java.util.function.Supplier; 75 76 /** 77 * A class which able to draw splash screen or snapshot as the starting window for a task. 78 * 79 * In order to speed up, there will use two threads to creating a splash screen in parallel. 80 * Right now we are still using PhoneWindow to create splash screen window, so the view is added to 81 * the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call 82 * scheduleTraversal to register a callback from Choreographer, so the drawing result of the view 83 * can synchronize on each frame. 84 * 85 * The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing 86 * the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background 87 * thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after 88 * the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very 89 * quickly. 90 * 91 * So basically we are using the spare time to prepare the SplashScreenView while splash screen 92 * thread is waiting for 93 * 1. WindowManager#addView(binder call to WM), 94 * 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device), 95 * 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will 96 * always happen before #draw). 97 * Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on 98 * splash-screen background tread can make they execute in parallel, which ensure it is faster then 99 * to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame. 100 * 101 * Here is the sequence to compare the difference between using single and two thread. 102 * 103 * Single thread: 104 * => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout 105 * -> draw -> AdaptiveIconDrawable#draw 106 * 107 * Two threads: 108 * => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw) 109 * => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint 110 * directly). 111 */ 112 @ShellSplashscreenThread 113 public class StartingSurfaceDrawer { 114 private static final String TAG = StartingWindowController.TAG; 115 116 private final Context mContext; 117 private final DisplayManager mDisplayManager; 118 private final ShellExecutor mSplashScreenExecutor; 119 @VisibleForTesting 120 final SplashscreenContentDrawer mSplashscreenContentDrawer; 121 private Choreographer mChoreographer; 122 private final WindowManagerGlobal mWindowManagerGlobal; 123 private StartingSurface.SysuiProxy mSysuiProxy; 124 private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo(); 125 126 private static final int LIGHT_BARS_MASK = 127 WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS 128 | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; 129 /** 130 * The minimum duration during which the splash screen is shown when the splash screen icon is 131 * animated. 132 */ 133 static final long MINIMAL_ANIMATION_DURATION = 400L; 134 135 /** 136 * Allow the icon style splash screen to be displayed for longer to give time for the animation 137 * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly 138 * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration. 139 */ 140 static final long TIME_WINDOW_DURATION = 100L; 141 142 /** 143 * The maximum duration during which the splash screen will be shown if the application is ready 144 * to show before the icon animation finishes. 145 */ 146 static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION; 147 148 /** 149 * @param splashScreenExecutor The thread used to control add and remove starting window. 150 */ StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor, IconProvider iconProvider, TransactionPool pool)151 public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor, 152 IconProvider iconProvider, TransactionPool pool) { 153 mContext = context; 154 mDisplayManager = mContext.getSystemService(DisplayManager.class); 155 mSplashScreenExecutor = splashScreenExecutor; 156 mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool); 157 mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance()); 158 mWindowManagerGlobal = WindowManagerGlobal.getInstance(); 159 mDisplayManager.getDisplay(DEFAULT_DISPLAY); 160 } 161 162 @VisibleForTesting 163 final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>(); 164 165 /** 166 * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is 167 * rendered and that have not yet been removed by their client. 168 */ 169 private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts = 170 new SparseArray<>(1); 171 getDisplay(int displayId)172 private Display getDisplay(int displayId) { 173 return mDisplayManager.getDisplay(displayId); 174 } 175 getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo)176 int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) { 177 return splashScreenThemeResId != 0 178 ? splashScreenThemeResId 179 : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource() 180 : com.android.internal.R.style.Theme_DeviceDefault_DayNight; 181 } 182 setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy)183 void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) { 184 mSysuiProxy = sysuiProxy; 185 } 186 187 /** 188 * Called when a task need a splash screen starting window. 189 * 190 * @param suggestType The suggestion type to draw the splash screen. 191 */ addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken, @StartingWindowType int suggestType)192 void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken, 193 @StartingWindowType int suggestType) { 194 final RunningTaskInfo taskInfo = windowInfo.taskInfo; 195 final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null 196 ? windowInfo.targetActivityInfo 197 : taskInfo.topActivityInfo; 198 if (activityInfo == null || activityInfo.packageName == null) { 199 return; 200 } 201 202 final int displayId = taskInfo.displayId; 203 final int taskId = taskInfo.taskId; 204 205 // replace with the default theme if the application didn't set 206 final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo); 207 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 208 "addSplashScreen for package: %s with theme: %s for task: %d, suggestType: %d", 209 activityInfo.packageName, Integer.toHexString(theme), taskId, suggestType); 210 final Display display = getDisplay(displayId); 211 if (display == null) { 212 // Can't show splash screen on requested display, so skip showing at all. 213 return; 214 } 215 Context context = displayId == DEFAULT_DISPLAY 216 ? mContext : mContext.createDisplayContext(display); 217 if (context == null) { 218 return; 219 } 220 if (theme != context.getThemeResId()) { 221 try { 222 context = context.createPackageContextAsUser(activityInfo.packageName, 223 CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId)); 224 context.setTheme(theme); 225 } catch (PackageManager.NameNotFoundException e) { 226 Slog.w(TAG, "Failed creating package context with package name " 227 + activityInfo.packageName + " for user " + taskInfo.userId, e); 228 return; 229 } 230 } 231 232 final Configuration taskConfig = taskInfo.getConfiguration(); 233 if (taskConfig.diffPublicOnly(context.getResources().getConfiguration()) != 0) { 234 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 235 "addSplashScreen: creating context based on task Configuration %s", 236 taskConfig); 237 final Context overrideContext = context.createConfigurationContext(taskConfig); 238 overrideContext.setTheme(theme); 239 final TypedArray typedArray = overrideContext.obtainStyledAttributes( 240 com.android.internal.R.styleable.Window); 241 final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0); 242 try { 243 if (resId != 0 && overrideContext.getDrawable(resId) != null) { 244 // We want to use the windowBackground for the override context if it is 245 // available, otherwise we use the default one to make sure a themed starting 246 // window is displayed for the app. 247 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 248 "addSplashScreen: apply overrideConfig %s", 249 taskConfig); 250 context = overrideContext; 251 } 252 } catch (Resources.NotFoundException e) { 253 Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: " 254 + taskId, e); 255 return; 256 } 257 typedArray.recycle(); 258 } 259 260 final WindowManager.LayoutParams params = new WindowManager.LayoutParams( 261 WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); 262 params.setFitInsetsSides(0); 263 params.setFitInsetsTypes(0); 264 params.format = suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN 265 ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; 266 int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED 267 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 268 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 269 final TypedArray a = context.obtainStyledAttributes(R.styleable.Window); 270 if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) { 271 windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 272 } 273 if (suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { 274 if (a.getBoolean(R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) { 275 windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; 276 } 277 } else { 278 windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; 279 } 280 params.layoutInDisplayCutoutMode = a.getInt( 281 R.styleable.Window_windowLayoutInDisplayCutoutMode, 282 params.layoutInDisplayCutoutMode); 283 params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0); 284 a.recycle(); 285 286 // Assumes it's safe to show starting windows of launched apps while 287 // the keyguard is being hidden. This is okay because starting windows never show 288 // secret information. 289 // TODO(b/113840485): Occluded may not only happen on default display 290 if (displayId == DEFAULT_DISPLAY && windowInfo.isKeyguardOccluded) { 291 windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; 292 } 293 294 // Force the window flags: this is a fake window, so it is not really 295 // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM 296 // flag because we do know that the next window will take input 297 // focus, so we want to get the IME window up on top of us right away. 298 // Touches will only pass through to the host activity window and will be blocked from 299 // passing to any other windows. 300 windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 301 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 302 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 303 params.flags = windowFlags; 304 params.token = appToken; 305 params.packageName = activityInfo.packageName; 306 params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; 307 308 if (!context.getResources().getCompatibilityInfo().supportsScreen()) { 309 params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; 310 } 311 312 params.setTitle("Splash Screen " + activityInfo.packageName); 313 314 // TODO(b/173975965) tracking performance 315 // Prepare the splash screen content view on splash screen worker thread in parallel, so the 316 // content view won't be blocked by binder call like addWindow and relayout. 317 // 1. Trigger splash screen worker thread to create SplashScreenView before/while 318 // Session#addWindow. 319 // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start 320 // traversal, which will call Session#relayout on splash screen thread. 321 // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at 322 // the same time the splash screen thread should be executing Session#relayout. Blocking the 323 // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready. 324 325 // Record whether create splash screen view success, notify to current thread after 326 // create splash screen view finished. 327 final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier(); 328 final FrameLayout rootLayout = new FrameLayout( 329 mSplashscreenContentDrawer.createViewContextWrapper(context)); 330 rootLayout.setPadding(0, 0, 0, 0); 331 rootLayout.setFitsSystemWindows(false); 332 final Runnable setViewSynchronized = () -> { 333 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView"); 334 // waiting for setContentView before relayoutWindow 335 SplashScreenView contentView = viewSupplier.get(); 336 final StartingWindowRecord record = mStartingWindowRecords.get(taskId); 337 // If record == null, either the starting window added fail or removed already. 338 // Do not add this view if the token is mismatch. 339 if (record != null && appToken == record.mAppToken) { 340 // if view == null then creation of content view was failed. 341 if (contentView != null) { 342 try { 343 rootLayout.addView(contentView); 344 } catch (RuntimeException e) { 345 Slog.w(TAG, "failed set content view to starting window " 346 + "at taskId: " + taskId, e); 347 contentView = null; 348 } 349 } 350 record.setSplashScreenView(contentView); 351 } 352 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 353 }; 354 if (mSysuiProxy != null) { 355 mSysuiProxy.requestTopUi(true, TAG); 356 } 357 mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo, 358 viewSupplier::setView, viewSupplier::setUiThreadInitTask); 359 try { 360 if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) { 361 // We use the splash screen worker thread to create SplashScreenView while adding 362 // the window, as otherwise Choreographer#doFrame might be delayed on this thread. 363 // And since Choreographer#doFrame won't happen immediately after adding the window, 364 // if the view is not added to the PhoneWindow on the first #doFrame, the view will 365 // not be rendered on the first frame. So here we need to synchronize the view on 366 // the window before first round relayoutWindow, which will happen after insets 367 // animation. 368 mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null); 369 final StartingWindowRecord record = mStartingWindowRecords.get(taskId); 370 record.parseAppSystemBarColor(context); 371 // Block until we get the background color. 372 final SplashScreenView contentView = viewSupplier.get(); 373 if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { 374 contentView.addOnAttachStateChangeListener( 375 new View.OnAttachStateChangeListener() { 376 @Override 377 public void onViewAttachedToWindow(View v) { 378 final int lightBarAppearance = ContrastColorUtil.isColorLight( 379 contentView.getInitBackgroundColor()) 380 ? LIGHT_BARS_MASK : 0; 381 contentView.getWindowInsetsController().setSystemBarsAppearance( 382 lightBarAppearance, LIGHT_BARS_MASK); 383 } 384 385 @Override 386 public void onViewDetachedFromWindow(View v) { 387 } 388 }); 389 } 390 record.mBGColor = contentView.getInitBackgroundColor(); 391 } else { 392 // release the icon view host 393 final SplashScreenView contentView = viewSupplier.get(); 394 if (contentView.getSurfaceHost() != null) { 395 SplashScreenView.releaseIconHost(contentView.getSurfaceHost()); 396 } 397 } 398 } catch (RuntimeException e) { 399 // don't crash if something else bad happens, for example a 400 // failure loading resources because we are loading from an app 401 // on external storage that has been unmounted. 402 Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e); 403 } 404 } 405 getStartingWindowBackgroundColorForTask(int taskId)406 int getStartingWindowBackgroundColorForTask(int taskId) { 407 final StartingWindowRecord startingWindowRecord = mStartingWindowRecords.get(taskId); 408 if (startingWindowRecord == null) { 409 return Color.TRANSPARENT; 410 } 411 return startingWindowRecord.mBGColor; 412 } 413 414 private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> { 415 private SplashScreenView mView; 416 private boolean mIsViewSet; 417 private Runnable mUiThreadInitTask; setView(SplashScreenView view)418 void setView(SplashScreenView view) { 419 synchronized (this) { 420 mView = view; 421 mIsViewSet = true; 422 notify(); 423 } 424 } 425 setUiThreadInitTask(Runnable initTask)426 void setUiThreadInitTask(Runnable initTask) { 427 synchronized (this) { 428 mUiThreadInitTask = initTask; 429 } 430 } 431 432 @Override 433 @Nullable get()434 public SplashScreenView get() { 435 synchronized (this) { 436 while (!mIsViewSet) { 437 try { 438 wait(); 439 } catch (InterruptedException ignored) { 440 } 441 } 442 if (mUiThreadInitTask != null) { 443 mUiThreadInitTask.run(); 444 mUiThreadInitTask = null; 445 } 446 return mView; 447 } 448 } 449 } 450 estimateTaskBackgroundColor(TaskInfo taskInfo)451 int estimateTaskBackgroundColor(TaskInfo taskInfo) { 452 if (taskInfo.topActivityInfo == null) { 453 return Color.TRANSPARENT; 454 } 455 final ActivityInfo activityInfo = taskInfo.topActivityInfo; 456 final String packageName = activityInfo.packageName; 457 final int userId = taskInfo.userId; 458 final Context windowContext; 459 try { 460 windowContext = mContext.createPackageContextAsUser( 461 packageName, Context.CONTEXT_RESTRICTED, UserHandle.of(userId)); 462 } catch (PackageManager.NameNotFoundException e) { 463 Slog.w(TAG, "Failed creating package context with package name " 464 + packageName + " for user " + taskInfo.userId, e); 465 return Color.TRANSPARENT; 466 } 467 try { 468 final IPackageManager packageManager = ActivityThread.getPackageManager(); 469 final String splashScreenThemeName = packageManager.getSplashScreenTheme(packageName, 470 userId); 471 final int splashScreenThemeId = splashScreenThemeName != null 472 ? windowContext.getResources().getIdentifier(splashScreenThemeName, null, null) 473 : 0; 474 475 final int theme = getSplashScreenTheme(splashScreenThemeId, activityInfo); 476 477 if (theme != windowContext.getThemeResId()) { 478 windowContext.setTheme(theme); 479 } 480 return mSplashscreenContentDrawer.estimateTaskBackgroundColor(windowContext); 481 } catch (RuntimeException | RemoteException e) { 482 Slog.w(TAG, "failed get starting window background color at taskId: " 483 + taskInfo.taskId, e); 484 } 485 return Color.TRANSPARENT; 486 } 487 488 /** 489 * Called when a task need a snapshot starting window. 490 */ makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, IBinder appToken, TaskSnapshot snapshot)491 void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, IBinder appToken, 492 TaskSnapshot snapshot) { 493 final int taskId = startingWindowInfo.taskInfo.taskId; 494 // Remove any existing starting window for this task before adding. 495 removeWindowNoAnimate(taskId); 496 final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken, 497 snapshot, mSplashScreenExecutor, () -> removeWindowNoAnimate(taskId)); 498 if (surface == null) { 499 return; 500 } 501 final StartingWindowRecord tView = new StartingWindowRecord(appToken, 502 null/* decorView */, surface, STARTING_WINDOW_TYPE_SNAPSHOT); 503 mStartingWindowRecords.put(taskId, tView); 504 } 505 506 /** 507 * Called when the content of a task is ready to show, starting window can be removed. 508 */ removeStartingWindow(StartingWindowRemovalInfo removalInfo)509 public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { 510 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 511 "Task start finish, remove starting surface for task: %d", 512 removalInfo.taskId); 513 removeWindowSynced(removalInfo, false /* immediately */); 514 } 515 516 /** 517 * Clear all starting windows immediately. 518 */ clearAllWindows()519 public void clearAllWindows() { 520 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 521 "Clear all starting windows immediately"); 522 final int taskSize = mStartingWindowRecords.size(); 523 final int[] taskIds = new int[taskSize]; 524 for (int i = taskSize - 1; i >= 0; --i) { 525 taskIds[i] = mStartingWindowRecords.keyAt(i); 526 } 527 for (int i = taskSize - 1; i >= 0; --i) { 528 removeWindowNoAnimate(taskIds[i]); 529 } 530 } 531 532 /** 533 * Called when the Task wants to copy the splash screen. 534 */ copySplashScreenView(int taskId)535 public void copySplashScreenView(int taskId) { 536 final StartingWindowRecord preView = mStartingWindowRecords.get(taskId); 537 SplashScreenViewParcelable parcelable; 538 SplashScreenView splashScreenView = preView != null ? preView.mContentView : null; 539 if (splashScreenView != null && splashScreenView.isCopyable()) { 540 parcelable = new SplashScreenViewParcelable(splashScreenView); 541 parcelable.setClientCallback( 542 new RemoteCallback((bundle) -> mSplashScreenExecutor.execute( 543 () -> onAppSplashScreenViewRemoved(taskId, false)))); 544 splashScreenView.onCopied(); 545 mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost()); 546 } else { 547 parcelable = null; 548 } 549 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 550 "Copying splash screen window view for task: %d with parcelable %b", 551 taskId, parcelable != null); 552 ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable); 553 } 554 555 /** 556 * Called when the {@link SplashScreenView} is removed from the client Activity view's hierarchy 557 * or when the Activity is clean up. 558 * 559 * @param taskId The Task id on which the splash screen was attached 560 */ onAppSplashScreenViewRemoved(int taskId)561 public void onAppSplashScreenViewRemoved(int taskId) { 562 onAppSplashScreenViewRemoved(taskId, true /* fromServer */); 563 } 564 565 /** 566 * @param fromServer If true, this means the removal was notified by the server. This is only 567 * used for debugging purposes. 568 * @see #onAppSplashScreenViewRemoved(int) 569 */ onAppSplashScreenViewRemoved(int taskId, boolean fromServer)570 private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) { 571 SurfaceControlViewHost viewHost = 572 mAnimatedSplashScreenSurfaceHosts.get(taskId); 573 if (viewHost == null) { 574 return; 575 } 576 mAnimatedSplashScreenSurfaceHosts.remove(taskId); 577 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 578 "%s the splash screen. Releasing SurfaceControlViewHost for task: %d", 579 fromServer ? "Server cleaned up" : "App removed", taskId); 580 SplashScreenView.releaseIconHost(viewHost); 581 } 582 addWindow(int taskId, IBinder appToken, View view, Display display, WindowManager.LayoutParams params, @StartingWindowType int suggestType)583 protected boolean addWindow(int taskId, IBinder appToken, View view, Display display, 584 WindowManager.LayoutParams params, @StartingWindowType int suggestType) { 585 boolean shouldSaveView = true; 586 final Context context = view.getContext(); 587 try { 588 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView"); 589 mWindowManagerGlobal.addView(view, params, display, 590 null /* parentWindow */, context.getUserId()); 591 } catch (WindowManager.BadTokenException e) { 592 // ignore 593 Slog.w(TAG, appToken + " already running, starting window not displayed. " 594 + e.getMessage()); 595 shouldSaveView = false; 596 } finally { 597 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 598 if (view.getParent() == null) { 599 Slog.w(TAG, "view not successfully added to wm, removing view"); 600 mWindowManagerGlobal.removeView(view, true /* immediate */); 601 shouldSaveView = false; 602 } 603 } 604 if (shouldSaveView) { 605 removeWindowNoAnimate(taskId); 606 saveSplashScreenRecord(appToken, taskId, view, suggestType); 607 } 608 return shouldSaveView; 609 } 610 611 @VisibleForTesting saveSplashScreenRecord(IBinder appToken, int taskId, View view, @StartingWindowType int suggestType)612 void saveSplashScreenRecord(IBinder appToken, int taskId, View view, 613 @StartingWindowType int suggestType) { 614 final StartingWindowRecord tView = new StartingWindowRecord(appToken, view, 615 null/* TaskSnapshotWindow */, suggestType); 616 mStartingWindowRecords.put(taskId, tView); 617 } 618 removeWindowNoAnimate(int taskId)619 private void removeWindowNoAnimate(int taskId) { 620 mTmpRemovalInfo.taskId = taskId; 621 removeWindowSynced(mTmpRemovalInfo, true /* immediately */); 622 } 623 onImeDrawnOnTask(int taskId)624 void onImeDrawnOnTask(int taskId) { 625 final StartingWindowRecord record = mStartingWindowRecords.get(taskId); 626 if (record != null && record.mTaskSnapshotWindow != null 627 && record.mTaskSnapshotWindow.hasImeSurface()) { 628 removeWindowNoAnimate(taskId); 629 } 630 } 631 removeWindowSynced(StartingWindowRemovalInfo removalInfo, boolean immediately)632 protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo, boolean immediately) { 633 final int taskId = removalInfo.taskId; 634 final StartingWindowRecord record = mStartingWindowRecords.get(taskId); 635 if (record != null) { 636 if (record.mDecorView != null) { 637 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 638 "Removing splash screen window for task: %d", taskId); 639 if (record.mContentView != null) { 640 record.clearSystemBarColor(); 641 if (immediately 642 || record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { 643 removeWindowInner(record.mDecorView, false); 644 } else { 645 if (removalInfo.playRevealAnimation) { 646 mSplashscreenContentDrawer.applyExitAnimation(record.mContentView, 647 removalInfo.windowAnimationLeash, removalInfo.mainFrame, 648 () -> removeWindowInner(record.mDecorView, true), 649 record.mCreateTime, removalInfo.roundedCornerRadius); 650 } else { 651 // the SplashScreenView has been copied to client, hide the view to skip 652 // default exit animation 653 removeWindowInner(record.mDecorView, true); 654 } 655 } 656 } else { 657 // shouldn't happen 658 Slog.e(TAG, "Found empty splash screen, remove!"); 659 removeWindowInner(record.mDecorView, false); 660 } 661 662 } 663 if (record.mTaskSnapshotWindow != null) { 664 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 665 "Removing task snapshot window for %d", taskId); 666 if (immediately) { 667 record.mTaskSnapshotWindow.removeImmediately(); 668 } else { 669 record.mTaskSnapshotWindow.scheduleRemove(removalInfo.deferRemoveForIme); 670 } 671 } 672 mStartingWindowRecords.remove(taskId); 673 } 674 } 675 removeWindowInner(View decorView, boolean hideView)676 private void removeWindowInner(View decorView, boolean hideView) { 677 if (mSysuiProxy != null) { 678 mSysuiProxy.requestTopUi(false, TAG); 679 } 680 if (hideView) { 681 decorView.setVisibility(View.GONE); 682 } 683 mWindowManagerGlobal.removeView(decorView, false /* immediate */); 684 } 685 686 /** 687 * Record the view or surface for a starting window. 688 */ 689 private static class StartingWindowRecord { 690 private final IBinder mAppToken; 691 private final View mDecorView; 692 private final TaskSnapshotWindow mTaskSnapshotWindow; 693 private SplashScreenView mContentView; 694 private boolean mSetSplashScreen; 695 @StartingWindowType private int mSuggestType; 696 private int mBGColor; 697 private final long mCreateTime; 698 private int mSystemBarAppearance; 699 private boolean mDrawsSystemBarBackgrounds; 700 StartingWindowRecord(IBinder appToken, View decorView, TaskSnapshotWindow taskSnapshotWindow, @StartingWindowType int suggestType)701 StartingWindowRecord(IBinder appToken, View decorView, 702 TaskSnapshotWindow taskSnapshotWindow, @StartingWindowType int suggestType) { 703 mAppToken = appToken; 704 mDecorView = decorView; 705 mTaskSnapshotWindow = taskSnapshotWindow; 706 if (mTaskSnapshotWindow != null) { 707 mBGColor = mTaskSnapshotWindow.getBackgroundColor(); 708 } 709 mSuggestType = suggestType; 710 mCreateTime = SystemClock.uptimeMillis(); 711 } 712 setSplashScreenView(SplashScreenView splashScreenView)713 private void setSplashScreenView(SplashScreenView splashScreenView) { 714 if (mSetSplashScreen) { 715 return; 716 } 717 mContentView = splashScreenView; 718 mSetSplashScreen = true; 719 } 720 parseAppSystemBarColor(Context context)721 private void parseAppSystemBarColor(Context context) { 722 final TypedArray a = context.obtainStyledAttributes(R.styleable.Window); 723 mDrawsSystemBarBackgrounds = a.getBoolean( 724 R.styleable.Window_windowDrawsSystemBarBackgrounds, false); 725 if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) { 726 mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; 727 } 728 if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) { 729 mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; 730 } 731 a.recycle(); 732 } 733 734 // Reset the system bar color which set by splash screen, make it align to the app. clearSystemBarColor()735 private void clearSystemBarColor() { 736 if (mDecorView == null || !mDecorView.isAttachedToWindow()) { 737 return; 738 } 739 if (mDecorView.getLayoutParams() instanceof WindowManager.LayoutParams) { 740 final WindowManager.LayoutParams lp = 741 (WindowManager.LayoutParams) mDecorView.getLayoutParams(); 742 if (mDrawsSystemBarBackgrounds) { 743 lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; 744 } else { 745 lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; 746 } 747 mDecorView.setLayoutParams(lp); 748 } 749 mDecorView.getWindowInsetsController().setSystemBarsAppearance( 750 mSystemBarAppearance, LIGHT_BARS_MASK); 751 } 752 } 753 } 754