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