From 56f65c3bb7d52422dc33108268207954d34633cb Mon Sep 17 00:00:00 2001
From: wilsonshih
Date: Tue, 1 Dec 2020 15:07:17 +0800
Subject: [PATCH] Customizable exit splash screen animation.(2/N)
Introduce new APIs to customize exit splash screen animation.
- SplashScreen
This can be used for Activity to handle the exit splash screen
animation.
- SplashScreen#OnExitAnimationListener
When receive onSplashScreenExit, the splash screen view will be on
top of the activity.
- SplashScreenView
The view object which represents the view of current starting window.
There are two APIs for developer to control it:
- getIconView: get the icon view object.
- remove: remove the view and release resources.
If an Activity create a OnExitAnimationListener and set on
SplashScreen, the core will request a copy of SplashScreenView
from Shell, then send the parcelable information to client to
reconstruct the view on top of the DecorView, then call
onSplashScreenExit to let the listener able to operating this view.
Bug: 73289295
Test: build/flash, check splash screen starting window.
Test: atest StartingSurfaceDrawerTests ActivityRecordTests
WindowOrganizerTests
Test: atest CtsWindowManagerDeviceTestCases:SplashscreenTests
Change-Id: I9f77fb4471bcf37a7f74d6c18a345ca56c05b716
---
core/api/current.txt | 18 +
core/api/test-current.txt | 1 +
core/java/android/app/Activity.java | 23 ++
core/java/android/app/ActivityClient.java | 15 +-
.../java/android/app/ActivityTaskManager.java | 18 +
core/java/android/app/ActivityThread.java | 120 ++++++
.../android/app/ClientTransactionHandler.java | 11 +
.../app/IActivityClientController.aidl | 5 +-
.../android/app/IActivityTaskManager.aidl | 7 +
.../servertransaction/ResumeActivityItem.java | 2 +-
.../TransferSplashScreenViewStateItem.java | 105 ++++++
core/java/android/view/ViewRootImpl.java | 14 +-
core/java/android/window/ITaskOrganizer.aidl | 5 +
core/java/android/window/SplashScreen.java | 188 ++++++++++
.../java/android/window/SplashScreenView.aidl | 20 +
.../java/android/window/SplashScreenView.java | 344 ++++++++++++++++++
core/java/android/window/TaskOrganizer.java | 11 +
core/res/res/layout/splash_screen_view.xml | 28 ++
core/res/res/values/symbols.xml | 2 +
.../android/wm/shell/ShellTaskOrganizer.java | 5 +
.../SplashscreenContentDrawer.java | 90 ++---
.../StartingSurfaceDrawer.java | 99 +++--
.../StartingSurfaceDrawerTests.java | 10 +-
.../server/wm/ActivityClientController.java | 16 +-
.../com/android/server/wm/ActivityRecord.java | 151 +++++++-
.../server/wm/ActivityTaskManagerService.java | 25 ++
.../core/java/com/android/server/wm/Task.java | 8 +
.../server/wm/TaskOrganizerController.java | 25 ++
.../server/wm/ActivityRecordTests.java | 2 +-
.../server/wm/WindowOrganizerTests.java | 12 +-
.../android/server/wm/WindowTestsBase.java | 3 +
31 files changed, 1266 insertions(+), 117 deletions(-)
create mode 100644 core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
create mode 100644 core/java/android/window/SplashScreen.java
create mode 100644 core/java/android/window/SplashScreenView.aidl
create mode 100644 core/java/android/window/SplashScreenView.java
create mode 100644 core/res/res/layout/splash_screen_view.xml
diff --git a/core/api/current.txt b/core/api/current.txt
index e264f1ddddb6a..ffe14c3f19542 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3865,6 +3865,7 @@ package android.app {
method @Nullable public android.net.Uri getReferrer();
method public int getRequestedOrientation();
method public final android.view.SearchEvent getSearchEvent();
+ method @NonNull public final android.window.SplashScreen getSplashScreen();
method public int getTaskId();
method public final CharSequence getTitle();
method public final int getTitleColor();
@@ -55823,6 +55824,23 @@ package android.widget.inline {
}
+package android.window {
+
+ public interface SplashScreen {
+ method public void setOnExitAnimationListener(@Nullable android.window.SplashScreen.OnExitAnimationListener);
+ }
+
+ public static interface SplashScreen.OnExitAnimationListener {
+ method public void onSplashScreenExit(@NonNull android.window.SplashScreenView);
+ }
+
+ public final class SplashScreenView extends android.widget.FrameLayout {
+ method @Nullable public android.view.View getIconView();
+ method public void remove();
+ }
+
+}
+
package javax.microedition.khronos.egl {
public interface EGL {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1be0fba570ab5..cefd1b153268b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2748,6 +2748,7 @@ package android.window {
public class TaskOrganizer extends android.window.WindowOrganizer {
ctor public TaskOrganizer();
method @BinderThread public void addStartingWindow(@NonNull android.window.StartingWindowInfo, @NonNull android.os.IBinder);
+ method @BinderThread public void copySplashScreenView(int);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void createRootTask(int, int, @Nullable android.os.IBinder);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean deleteRootTask(@NonNull android.window.WindowContainerToken);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4728f11a402d8..992d054737b9a 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -139,6 +139,8 @@ import android.view.translation.UiTranslationController;
import android.widget.AdapterView;
import android.widget.Toast;
import android.widget.Toolbar;
+import android.window.SplashScreen;
+import android.window.SplashScreenView;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -961,6 +963,10 @@ public class Activity extends ContextThemeWrapper
private UiTranslationController mUiTranslationController;
+ private SplashScreen mSplashScreen;
+ /** @hide */
+ SplashScreenView mSplashScreenView;
+
private final WindowControllerCallback mWindowControllerCallback =
new WindowControllerCallback() {
/**
@@ -1602,6 +1608,23 @@ public class Activity extends ContextThemeWrapper
}
+ /**
+ * Get the interface that activity use to talk to the splash screen.
+ * @see SplashScreen
+ */
+ public final @NonNull SplashScreen getSplashScreen() {
+ return getOrCreateSplashScreen();
+ }
+
+ private SplashScreen getOrCreateSplashScreen() {
+ synchronized (this) {
+ if (mSplashScreen == null) {
+ mSplashScreen = new SplashScreen.SplashScreenImpl(this);
+ }
+ return mSplashScreen;
+ }
+ }
+
/**
* Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with
* the attribute {@link android.R.attr#persistableMode} set to
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 401f8cc13bad8..e3b5e9a32324e 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -46,9 +46,9 @@ public class ActivityClient {
}
/** Reports {@link Activity#onResume()} is done. */
- public void activityResumed(IBinder token) {
+ public void activityResumed(IBinder token, boolean handleSplashScreenExit) {
try {
- getActivityClientController().activityResumed(token);
+ getActivityClientController().activityResumed(token, handleSplashScreenExit);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -488,6 +488,17 @@ public class ActivityClient {
}
}
+ /**
+ * Reports the splash screen view has attached to client.
+ */
+ void reportSplashScreenAttached(IBinder token) {
+ try {
+ getActivityClientController().splashScreenAttached(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
public static ActivityClient getInstance() {
return sInstance.get();
}
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 70fa4445479ba..233f737b8e0f7 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -19,6 +19,7 @@ package android.app;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -37,6 +38,7 @@ import android.os.ServiceManager;
import android.util.DisplayMetrics;
import android.util.Singleton;
import android.view.RemoteAnimationDefinition;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
import java.util.List;
@@ -242,6 +244,22 @@ public class ActivityTaskManager {
return sMaxRecentTasks;
}
+ /**
+ * Notify the server that splash screen of the given task has been copied"
+ *
+ * @param taskId Id of task to handle the material to reconstruct the splash screen view.
+ * @param parcelable Used to reconstruct the view, null means the surface is un-copyable.
+ * @hide
+ */
+ public void onSplashScreenViewCopyFinished(int taskId,
+ @Nullable SplashScreenViewParcelable parcelable) {
+ try {
+ getService().onSplashScreenViewCopyFinished(taskId, parcelable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Return the default limit on the number of recents that an app can make.
* @hide
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index bb6a774cbee25..3d9f6123963f6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -171,12 +171,15 @@ import android.view.View;
import android.view.ViewDebug;
import android.view.ViewManager;
import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.autofill.AutofillId;
import android.view.translation.TranslationSpec;
import android.webkit.WebView;
+import android.window.SplashScreen;
+import android.window.SplashScreenView;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -185,6 +188,7 @@ import com.android.internal.content.ReferrerIntent;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SomeArgs;
+import com.android.internal.policy.DecorView;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.Preconditions;
@@ -227,6 +231,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@@ -285,6 +290,7 @@ public final class ActivityThread extends ClientTransactionHandler {
/** Use background GC policy and default JIT threshold. */
private static final int VM_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;
+ private static final int REMOVE_SPLASH_SCREEN_VIEW_TIMEOUT = 5000;
/**
* Denotes an invalid sequence number corresponding to a process state change.
*/
@@ -486,6 +492,8 @@ public final class ActivityThread extends ClientTransactionHandler {
final ArrayMap> mOnPauseListeners
= new ArrayMap>();
+ private SplashScreen.SplashScreenManagerGlobal mSplashScreenGlobal;
+
final GcIdler mGcIdler = new GcIdler();
final PurgeIdler mPurgeIdler = new PurgeIdler();
@@ -1930,6 +1938,8 @@ public final class ActivityThread extends ClientTransactionHandler {
public static final int INSTRUMENT_WITHOUT_RESTART = 170;
public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
+ public static final int REMOVE_SPLASH_SCREEN_VIEW = 172;
+
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
@@ -1976,6 +1986,8 @@ public final class ActivityThread extends ClientTransactionHandler {
case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART";
case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
return "FINISH_INSTRUMENTATION_WITHOUT_RESTART";
+ case REMOVE_SPLASH_SCREEN_VIEW:
+ return "REMOVE_SPLASH_SCREEN_VIEW";
}
}
return Integer.toString(code);
@@ -2169,6 +2181,9 @@ public final class ActivityThread extends ClientTransactionHandler {
case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
handleFinishInstrumentationWithoutRestart();
break;
+ case REMOVE_SPLASH_SCREEN_VIEW:
+ handleRemoveSplashScreenView((ActivityClientRecord) msg.obj);
+ break;
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
@@ -3954,6 +3969,106 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
+ /**
+ * Register a splash screen manager to this process.
+ */
+ public void registerSplashScreenManager(
+ @NonNull SplashScreen.SplashScreenManagerGlobal manager) {
+ synchronized (this) {
+ mSplashScreenGlobal = manager;
+ }
+ }
+
+ @Override
+ public boolean isHandleSplashScreenExit(@NonNull IBinder token) {
+ synchronized (this) {
+ return mSplashScreenGlobal != null && mSplashScreenGlobal.containsExitListener(token);
+ }
+ }
+
+ @Override
+ public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
+ @Nullable SplashScreenView.SplashScreenViewParcelable parcelable) {
+ final DecorView decorView = (DecorView) r.window.peekDecorView();
+ if (parcelable != null && decorView != null) {
+ createSplashScreen(r, decorView, parcelable);
+ } else {
+ // shouldn't happen!
+ Slog.e(TAG, "handleAttachSplashScreenView failed, unable to attach");
+ }
+ }
+
+ private void createSplashScreen(ActivityClientRecord r, DecorView decorView,
+ SplashScreenView.SplashScreenViewParcelable parcelable) {
+ final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity);
+ final SplashScreenView view = builder.createFromParcel(parcelable).build();
+ decorView.addView(view);
+ view.cacheRootWindow(r.window);
+ view.makeSystemUIColorsTransparent();
+ r.activity.mSplashScreenView = view;
+ view.requestLayout();
+ // Ensure splash screen view is shown before remove the splash screen window.
+ final ViewRootImpl impl = decorView.getViewRootImpl();
+ final boolean hardwareEnabled = impl != null && impl.isHardwareEnabled();
+ final AtomicBoolean notified = new AtomicBoolean();
+ if (hardwareEnabled) {
+ final Runnable frameCommit = new Runnable() {
+ @Override
+ public void run() {
+ view.post(() -> {
+ if (!notified.get()) {
+ view.getViewTreeObserver().unregisterFrameCommitCallback(this);
+ ActivityClient.getInstance().reportSplashScreenAttached(
+ r.token);
+ notified.set(true);
+ }
+ });
+ }
+ };
+ view.getViewTreeObserver().registerFrameCommitCallback(frameCommit);
+ } else {
+ final ViewTreeObserver.OnDrawListener onDrawListener =
+ new ViewTreeObserver.OnDrawListener() {
+ @Override
+ public void onDraw() {
+ view.post(() -> {
+ if (!notified.get()) {
+ view.getViewTreeObserver().removeOnDrawListener(this);
+ ActivityClient.getInstance().reportSplashScreenAttached(
+ r.token);
+ notified.set(true);
+ }
+ });
+ }
+ };
+ view.getViewTreeObserver().addOnDrawListener(onDrawListener);
+ }
+ }
+
+ @Override
+ public void handOverSplashScreenView(@NonNull ActivityClientRecord r) {
+ if (r.activity.mSplashScreenView != null) {
+ Message msg = mH.obtainMessage(H.REMOVE_SPLASH_SCREEN_VIEW, r);
+ mH.sendMessageDelayed(msg, REMOVE_SPLASH_SCREEN_VIEW_TIMEOUT);
+ synchronized (this) {
+ if (mSplashScreenGlobal != null) {
+ mSplashScreenGlobal.dispatchOnExitAnimation(r.token,
+ r.activity.mSplashScreenView);
+ }
+ }
+ }
+ }
+
+ /**
+ * Force remove splash screen view.
+ */
+ private void handleRemoveSplashScreenView(@NonNull ActivityClientRecord r) {
+ if (r.activity.mSplashScreenView != null) {
+ r.activity.mSplashScreenView.remove();
+ r.activity.mSplashScreenView = null;
+ }
+ }
+
/**
* Cycle activity through onPause and onUserLeaveHint so that PIP is entered if supported, then
* return to its previous state. This allows activities that rely on onUserLeaveHint instead of
@@ -5174,6 +5289,11 @@ public final class ActivityThread extends ClientTransactionHandler {
r.setState(ON_DESTROY);
mLastReportedWindowingMode.remove(r.activity.getActivityToken());
schedulePurgeIdler();
+ synchronized (this) {
+ if (mSplashScreenGlobal != null) {
+ mSplashScreenGlobal.tokenDestroyed(r.token);
+ }
+ }
// updatePendingActivityConfiguration() reads from mActivities to update
// ActivityClientRecord which runs in a different thread. Protect modifications to
// mActivities to avoid race.
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 0e1c827145d2d..cf5fd148c0302 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -28,6 +28,7 @@ import android.content.res.Configuration;
import android.os.IBinder;
import android.util.MergedConfiguration;
import android.view.DisplayAdjustments.FixedRotationAdjustments;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.ReferrerIntent;
@@ -158,6 +159,16 @@ public abstract class ClientTransactionHandler {
/** Request that an activity enter picture-in-picture. */
public abstract void handlePictureInPictureRequested(@NonNull ActivityClientRecord r);
+ /** Whether the activity want to handle splash screen exit animation */
+ public abstract boolean isHandleSplashScreenExit(@NonNull IBinder token);
+
+ /** Attach a splash screen window view to the top of the activity */
+ public abstract void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
+ @NonNull SplashScreenViewParcelable parcelable);
+
+ /** Hand over the splash screen window view to the activity */
+ public abstract void handOverSplashScreenView(@NonNull ActivityClientRecord r);
+
/** Perform activity launch. */
public abstract Activity handleLaunchActivity(@NonNull ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent);
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 9d3286fa271c3..bb743b89e00fe 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -34,7 +34,7 @@ import com.android.internal.policy.IKeyguardDismissCallback;
*/
interface IActivityClientController {
oneway void activityIdle(in IBinder token, in Configuration config, in boolean stopProfiling);
- oneway void activityResumed(in IBinder token);
+ oneway void activityResumed(in IBinder token, in boolean handleSplashScreenExit);
oneway void activityTopResumedStateLost();
/**
* Notifies that the activity has completed paused. This call is not one-way because it can make
@@ -142,4 +142,7 @@ interface IActivityClientController {
* on the back stack.
*/
oneway void onBackPressedOnTaskRoot(in IBinder token);
+
+ /** Reports that the splash screen view has attached to activity. */
+ oneway void splashScreenAttached(in IBinder token);
}
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 38a3e70b37426..542f754ce3646 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -70,6 +70,7 @@ import android.view.IRecentsAnimationRunner;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationAdapter;
import android.window.IWindowOrganizerController;
+import android.window.SplashScreenView;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.os.IResultReceiver;
@@ -348,4 +349,10 @@ interface IActivityTaskManager {
* Clears launch params for given packages.
*/
void clearLaunchParamsForPackages(in List packageNames);
+
+ /**
+ * A splash screen view has copied.
+ */
+ void onSplashScreenViewCopyFinished(int taskId,
+ in SplashScreenView.SplashScreenViewParcelable material);
}
diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java
index d451599cc7b05..e6fdc006615a1 100644
--- a/core/java/android/app/servertransaction/ResumeActivityItem.java
+++ b/core/java/android/app/servertransaction/ResumeActivityItem.java
@@ -60,7 +60,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem {
public void postExecute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
// TODO(lifecycler): Use interface callback instead of actual implementation.
- ActivityClient.getInstance().activityResumed(token);
+ ActivityClient.getInstance().activityResumed(token, client.isHandleSplashScreenExit(token));
}
@Override
diff --git a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
new file mode 100644
index 0000000000000..5374984d31d08
--- /dev/null
+++ b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.app.ClientTransactionHandler;
+import android.os.Parcel;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Transfer a splash screen view to an Activity.
+ * @hide
+ */
+public class TransferSplashScreenViewStateItem extends ActivityTransactionItem {
+
+ private SplashScreenViewParcelable mSplashScreenViewParcelable;
+ private @TransferRequest int mRequest;
+
+ @IntDef(value = {
+ ATTACH_TO,
+ HANDOVER_TO
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TransferRequest {}
+ // request client to attach the view on it.
+ public static final int ATTACH_TO = 0;
+ // tell client that you can handle the splash screen view.
+ public static final int HANDOVER_TO = 1;
+
+ @Override
+ public void execute(@NonNull ClientTransactionHandler client,
+ @NonNull ActivityThread.ActivityClientRecord r,
+ PendingTransactionActions pendingActions) {
+ switch (mRequest) {
+ case ATTACH_TO:
+ client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable);
+ break;
+ case HANDOVER_TO:
+ client.handOverSplashScreenView(r);
+ break;
+ }
+ }
+
+ @Override
+ public void recycle() {
+ ObjectPool.recycle(this);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mRequest);
+ dest.writeTypedObject(mSplashScreenViewParcelable, flags);
+ }
+
+ private TransferSplashScreenViewStateItem() {}
+ private TransferSplashScreenViewStateItem(Parcel in) {
+ mRequest = in.readInt();
+ mSplashScreenViewParcelable = in.readTypedObject(SplashScreenViewParcelable.CREATOR);
+ }
+
+ /** Obtain an instance initialized with provided params. */
+ public static TransferSplashScreenViewStateItem obtain(@TransferRequest int state,
+ @Nullable SplashScreenViewParcelable parcelable) {
+ TransferSplashScreenViewStateItem instance =
+ ObjectPool.obtain(TransferSplashScreenViewStateItem.class);
+ if (instance == null) {
+ instance = new TransferSplashScreenViewStateItem();
+ }
+ instance.mRequest = state;
+ instance.mSplashScreenViewParcelable = parcelable;
+
+ return instance;
+ }
+
+ public static final @NonNull Creator CREATOR =
+ new Creator() {
+ public TransferSplashScreenViewStateItem createFromParcel(Parcel in) {
+ return new TransferSplashScreenViewStateItem(in);
+ }
+
+ public TransferSplashScreenViewStateItem[] newArray(int size) {
+ return new TransferSplashScreenViewStateItem[size];
+ }
+ };
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6b13a290b20ee..228ee3c76b144 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2836,8 +2836,7 @@ public final class ViewRootImpl implements ViewParent,
mScroller.abortAnimation();
}
// Our surface is gone
- if (mAttachInfo.mThreadedRenderer != null &&
- mAttachInfo.mThreadedRenderer.isEnabled()) {
+ if (isHardwareEnabled()) {
mAttachInfo.mThreadedRenderer.destroy();
}
} else if ((surfaceReplaced
@@ -3923,8 +3922,15 @@ public final class ViewRootImpl implements ViewParent,
};
}
+ /**
+ * @hide
+ */
+ public boolean isHardwareEnabled() {
+ return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
+ }
+
private boolean addFrameCompleteCallbackIfNeeded() {
- if (mAttachInfo.mThreadedRenderer == null || !mAttachInfo.mThreadedRenderer.isEnabled()) {
+ if (!isHardwareEnabled()) {
return false;
}
@@ -4268,7 +4274,7 @@ public final class ViewRootImpl implements ViewParent,
boolean useAsyncReport = false;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
- if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
+ if (isHardwareEnabled()) {
// If accessibility focus moved, always invalidate the root.
boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
mInvalidateRootRequested = false;
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index 88b2257a55b1a..8f541d0bd1940 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -41,6 +41,11 @@ oneway interface ITaskOrganizer {
*/
void removeStartingWindow(int taskId);
+ /**
+ * Called when the Task want to copy the splash screen.
+ */
+ void copySplashScreenView(int taskId);
+
/**
* A callback when the Task is available for the registered organizer. The client is responsible
* for releasing the SurfaceControl in the callback. For non-root tasks, the leash may initially
diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java
new file mode 100644
index 0000000000000..4b88a9bc39de7
--- /dev/null
+++ b/core/java/android/window/SplashScreen.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.os.IBinder;
+import android.util.Singleton;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/**
+ * The interface that apps use to talk to the splash screen.
+ *
+ * Each splash screen instance is bound to a particular {@link Activity}.
+ * To obtain a {@link SplashScreen} for an Activity, use
+ * Activity.getSplashScreen() to get the SplashScreen.
+ */
+public interface SplashScreen {
+ /**
+ * Specifies whether an {@link Activity} wants to handle the splash screen animation on its
+ * own. Normally the splash screen will show on screen before the content of the activity has
+ * been drawn, and disappear when the activity is showing on the screen. With this listener set,
+ * the activity will receive {@link OnExitAnimationListener#onSplashScreenExit} callback if
+ * splash screen is showed, then the activity can create its own exit animation based on the
+ * SplashScreenView.
+ *
+ * Note that this method must be called before splash screen leave, so it only takes effect
+ * during or before {@link Activity#onResume}.
+ *
+ * @param listener the listener for receive the splash screen with
+ *
+ * @see OnExitAnimationListener#onSplashScreenExit(SplashScreenView)
+ */
+ @SuppressLint("ExecutorRegistration")
+ void setOnExitAnimationListener(@Nullable SplashScreen.OnExitAnimationListener listener);
+
+ /**
+ * Listens for the splash screen exit event.
+ */
+ interface OnExitAnimationListener {
+ /**
+ * When receiving this callback, the {@link SplashScreenView} object will be drawing on top
+ * of the activity. The {@link SplashScreenView} represents the splash screen view
+ * object, developer can make an exit animation based on this view.
+ *
+ * If {@link SplashScreenView#remove} is not called after 5000ms, the method will be
+ * automatically called and the splash screen removed.
+ *
+ * This method is never invoked if your activity sets
+ * {@link #setOnExitAnimationListener} to null..
+ *
+ * @param view The view object which on top of this Activity.
+ * @see #setOnExitAnimationListener
+ */
+ void onSplashScreenExit(@NonNull SplashScreenView view);
+ }
+
+ /**
+ * @hide
+ */
+ class SplashScreenImpl implements SplashScreen {
+ private OnExitAnimationListener mExitAnimationListener;
+ private final IBinder mActivityToken;
+ private final SplashScreenManagerGlobal mGlobal;
+
+ public SplashScreenImpl(Context context) {
+ mActivityToken = context.getActivityToken();
+ mGlobal = SplashScreenManagerGlobal.getInstance();
+ }
+
+ @Override
+ public void setOnExitAnimationListener(
+ @Nullable SplashScreen.OnExitAnimationListener listener) {
+ if (mActivityToken == null) {
+ // This is not an activity.
+ return;
+ }
+ synchronized (mGlobal.mGlobalLock) {
+ mExitAnimationListener = listener;
+ if (listener != null) {
+ mGlobal.addImpl(this);
+ } else {
+ mGlobal.removeImpl(this);
+ }
+ }
+ }
+ }
+
+ /**
+ * This class is only used internally to manage the activities for this process.
+ *
+ * @hide
+ */
+ class SplashScreenManagerGlobal {
+ private static final String TAG = SplashScreen.class.getSimpleName();
+ private final Object mGlobalLock = new Object();
+ private final ArrayList mImpls = new ArrayList<>();
+
+ private SplashScreenManagerGlobal() {
+ ActivityThread.currentActivityThread().registerSplashScreenManager(this);
+ }
+
+ public static SplashScreenManagerGlobal getInstance() {
+ return sInstance.get();
+ }
+
+ private static final Singleton sInstance =
+ new Singleton() {
+ @Override
+ protected SplashScreenManagerGlobal create() {
+ return new SplashScreenManagerGlobal();
+ }
+ };
+
+ private void addImpl(SplashScreenImpl impl) {
+ synchronized (mGlobalLock) {
+ mImpls.add(impl);
+ }
+ }
+
+ private void removeImpl(SplashScreenImpl impl) {
+ synchronized (mGlobalLock) {
+ mImpls.remove(impl);
+ }
+ }
+
+ private SplashScreenImpl findImpl(IBinder token) {
+ synchronized (mGlobalLock) {
+ for (SplashScreenImpl impl : mImpls) {
+ if (impl.mActivityToken == token) {
+ return impl;
+ }
+ }
+ }
+ return null;
+ }
+
+ public void tokenDestroyed(IBinder token) {
+ synchronized (mGlobalLock) {
+ final SplashScreenImpl impl = findImpl(token);
+ if (impl != null) {
+ removeImpl(impl);
+ }
+ }
+ }
+
+ public void dispatchOnExitAnimation(IBinder token, SplashScreenView view) {
+ synchronized (mGlobalLock) {
+ final SplashScreenImpl impl = findImpl(token);
+ if (impl == null) {
+ return;
+ }
+ if (impl.mExitAnimationListener == null) {
+ Slog.e(TAG, "cannot dispatch onExitAnimation to listener " + token);
+ return;
+ }
+ impl.mExitAnimationListener.onSplashScreenExit(view);
+ }
+ }
+
+ public boolean containsExitListener(IBinder token) {
+ synchronized (mGlobalLock) {
+ final SplashScreenImpl impl = findImpl(token);
+ return impl != null && impl.mExitAnimationListener != null;
+ }
+ }
+ }
+}
diff --git a/core/java/android/window/SplashScreenView.aidl b/core/java/android/window/SplashScreenView.aidl
new file mode 100644
index 0000000000000..cc7ac1edce805
--- /dev/null
+++ b/core/java/android/window/SplashScreenView.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/** @hide */
+parcelable SplashScreenView.SplashScreenViewParcelable;
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
new file mode 100644
index 0000000000000..b8d3fc2eda05b
--- /dev/null
+++ b/core/java/android/window/SplashScreenView.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.window;
+
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+import com.android.internal.policy.DecorView;
+
+/**
+ * The view which allows an activity to customize its splash screen exit animation.
+ *
+ * Activities will receive this view as a parameter of
+ * {@link SplashScreen.OnExitAnimationListener#onSplashScreenExit} if
+ * they set {@link SplashScreen#setOnExitAnimationListener}.
+ * When this callback is called, this view will be on top of the activity.
+ *
+ * This view is composed of a view containing the splashscreen icon (see
+ * windowSplashscreenAnimatedIcon) and a background.
+ * Developers can use {@link #getIconView} to get this view and replace the drawable or
+ * add animation to it. The background of this view is filled with a single color, which can be
+ * edited during the animation by {@link View#setBackground} or {@link View#setBackgroundColor}.
+ *
+ * @see SplashScreen
+ */
+public final class SplashScreenView extends FrameLayout {
+ private static final String TAG = SplashScreenView.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private boolean mNotCopyable;
+ private int mInitBackgroundColor;
+ private View mIconView;
+ private Bitmap mParceledBitmap;
+ // cache original window and status
+ private Window mWindow;
+ private boolean mDrawBarBackground;
+ private int mStatusBarColor;
+ private int mNavigationBarColor;
+
+ /**
+ * Internal builder to create a SplashScreenWindowView object.
+ * @hide
+ */
+ public static class Builder {
+ private final Context mContext;
+ private int mIconSize;
+ private @ColorInt int mBackgroundColor;
+ private Bitmap mParceledBitmap;
+ private Drawable mIconDrawable;
+
+ public Builder(@NonNull Context context) {
+ mContext = context;
+ }
+
+ /**
+ * When create from {@link SplashScreenViewParcelable}, all the materials were be settled so
+ * you do not need to call other set methods.
+ */
+ public Builder createFromParcel(SplashScreenViewParcelable parcelable) {
+ mIconSize = parcelable.getIconSize();
+ mBackgroundColor = parcelable.getBackgroundColor();
+ if (parcelable.getBitmap() != null) {
+ mIconDrawable = new BitmapDrawable(mContext.getResources(), parcelable.getBitmap());
+ mParceledBitmap = parcelable.getBitmap();
+ }
+ return this;
+ }
+
+ /**
+ * Set the rectangle size for the center view.
+ */
+ public Builder setIconSize(int iconSize) {
+ mIconSize = iconSize;
+ return this;
+ }
+
+ /**
+ * Set the background color for the view.
+ */
+ public Builder setBackgroundColor(@ColorInt int backgroundColor) {
+ mBackgroundColor = backgroundColor;
+ return this;
+ }
+
+ /**
+ * Set the Drawable object to fill the center view.
+ */
+ public Builder setCenterViewDrawable(Drawable drawable) {
+ mIconDrawable = drawable;
+ return this;
+ }
+
+ /**
+ * Create SplashScreenWindowView object from materials.
+ */
+ public SplashScreenView build() {
+ final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+ final SplashScreenView view = (SplashScreenView)
+ layoutInflater.inflate(R.layout.splash_screen_view, null, false);
+ view.mInitBackgroundColor = mBackgroundColor;
+ view.setBackgroundColor(mBackgroundColor);
+ view.mIconView = view.findViewById(R.id.splashscreen_icon_view);
+ if (mIconSize != 0) {
+ final ViewGroup.LayoutParams params = view.mIconView.getLayoutParams();
+ params.width = mIconSize;
+ params.height = mIconSize;
+ view.mIconView.setLayoutParams(params);
+ }
+ if (mIconDrawable != null) {
+ view.mIconView.setBackground(mIconDrawable);
+ }
+ if (mParceledBitmap != null) {
+ view.mParceledBitmap = mParceledBitmap;
+ }
+ if (DEBUG) {
+ Log.d(TAG, " build " + view + " center view? " + view.mIconView
+ + " iconSize " + mIconSize);
+ }
+ return view;
+ }
+ }
+
+ /** @hide */
+ public SplashScreenView(Context context) {
+ super(context);
+ }
+
+ /** @hide */
+ public SplashScreenView(Context context, AttributeSet attributeSet) {
+ super(context, attributeSet);
+ }
+
+ /**
+ * Declared this view is not copyable.
+ * @hide
+ */
+ public void setNotCopyable() {
+ mNotCopyable = true;
+ }
+
+ /**
+ * Whether this view is copyable.
+ * @hide
+ */
+ public boolean isCopyable() {
+ return !mNotCopyable;
+ }
+
+ /**
+ * Remove this view and release its resource.
+ * Do not invoke this method from a drawing method
+ * ({@link #onDraw(android.graphics.Canvas)} for instance).
+ */
+ public void remove() {
+ setVisibility(GONE);
+ if (mParceledBitmap != null) {
+ mIconView.setBackground(null);
+ mParceledBitmap.recycle();
+ mParceledBitmap = null;
+ }
+ if (mWindow != null) {
+ final DecorView decorView = (DecorView) mWindow.peekDecorView();
+ if (DEBUG) {
+ Log.d(TAG, "remove starting view");
+ }
+ if (decorView != null) {
+ decorView.removeView(this);
+ }
+ restoreSystemUIColors();
+ mWindow = null;
+ }
+ }
+
+ /**
+ * Cache the root window.
+ * @hide
+ */
+ public void cacheRootWindow(Window window) {
+ mWindow = window;
+ }
+
+ /**
+ * Called after SplashScreenView has added on the root window.
+ * @hide
+ */
+ public void makeSystemUIColorsTransparent() {
+ if (mWindow != null) {
+ final WindowManager.LayoutParams attr = mWindow.getAttributes();
+ mDrawBarBackground = (attr.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+ mWindow.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ mStatusBarColor = mWindow.getStatusBarColor();
+ mNavigationBarColor = mWindow.getNavigationBarDividerColor();
+ mWindow.setStatusBarColor(Color.TRANSPARENT);
+ mWindow.setNavigationBarColor(Color.TRANSPARENT);
+ }
+ setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+ }
+
+ private void restoreSystemUIColors() {
+ if (mWindow != null) {
+ if (!mDrawBarBackground) {
+ mWindow.clearFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ }
+ mWindow.setStatusBarColor(mStatusBarColor);
+ mWindow.setNavigationBarColor(mNavigationBarColor);
+ }
+ }
+
+ /**
+ * Get the view containing the Splash Screen icon and its background.
+ */
+ public @Nullable View getIconView() {
+ return mIconView;
+ }
+
+ /**
+ * Get the initial background color of this view.
+ * @hide
+ */
+ @ColorInt int getInitBackgroundColor() {
+ return mInitBackgroundColor;
+ }
+
+ /**
+ * Use to create {@link SplashScreenView} object across process.
+ * @hide
+ */
+ public static class SplashScreenViewParcelable implements Parcelable {
+ private int mIconSize;
+ private int mBackgroundColor;
+ private Bitmap mBitmap;
+
+ public SplashScreenViewParcelable(SplashScreenView view) {
+ final ViewGroup.LayoutParams params = view.getIconView().getLayoutParams();
+ mIconSize = params.height;
+ mBackgroundColor = view.getInitBackgroundColor();
+
+ final Drawable background = view.getIconView().getBackground();
+ if (background != null) {
+ final Rect initialBounds = background.copyBounds();
+ final int width = initialBounds.width();
+ final int height = initialBounds.height();
+
+ final Bitmap iconSnapshot = Bitmap.createBitmap(width, height,
+ Bitmap.Config.ARGB_8888);
+ final Canvas bmpCanvas = new Canvas(iconSnapshot);
+ background.setBounds(0, 0, width, height);
+ background.draw(bmpCanvas);
+ mBitmap = iconSnapshot.createAshmemBitmap();
+ iconSnapshot.recycle();
+ }
+ }
+
+ private SplashScreenViewParcelable(@NonNull Parcel source) {
+ readParcel(source);
+ }
+
+ private void readParcel(@NonNull Parcel source) {
+ mIconSize = source.readInt();
+ mBackgroundColor = source.readInt();
+ mBitmap = source.readTypedObject(Bitmap.CREATOR);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mIconSize);
+ dest.writeInt(mBackgroundColor);
+ dest.writeTypedObject(mBitmap, flags);
+ }
+
+ public static final @NonNull Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+ public SplashScreenViewParcelable createFromParcel(@NonNull Parcel source) {
+ return new SplashScreenViewParcelable(source);
+ }
+ public SplashScreenViewParcelable[] newArray(int size) {
+ return new SplashScreenViewParcelable[size];
+ }
+ };
+
+ /**
+ * Release the bitmap if another process cannot handle it.
+ */
+ public void clearIfNeeded() {
+ if (mBitmap != null) {
+ mBitmap.recycle();
+ mBitmap = null;
+ }
+ }
+
+ int getIconSize() {
+ return mIconSize;
+ }
+
+ int getBackgroundColor() {
+ return mBackgroundColor;
+ }
+
+ Bitmap getBitmap() {
+ return mBitmap;
+ }
+ }
+}
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index cdb4762a4f0ae..217ade82b336d 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -104,6 +104,12 @@ public class TaskOrganizer extends WindowOrganizer {
@BinderThread
public void removeStartingWindow(int taskId) {}
+ /**
+ * Called when the Task want to copy the splash screen.
+ */
+ @BinderThread
+ public void copySplashScreenView(int taskId) {}
+
/**
* Called when a task with the registered windowing mode can be controlled by this task
* organizer. For non-root tasks, the leash may initially be hidden so it is up to the organizer
@@ -222,6 +228,11 @@ public class TaskOrganizer extends WindowOrganizer {
mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(taskId));
}
+ @Override
+ public void copySplashScreenView(int taskId) {
+ mExecutor.execute(() -> TaskOrganizer.this.copySplashScreenView(taskId));
+ }
+
@Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
mExecutor.execute(() -> TaskOrganizer.this.onTaskAppeared(taskInfo, leash));
diff --git a/core/res/res/layout/splash_screen_view.xml b/core/res/res/layout/splash_screen_view.xml
new file mode 100644
index 0000000000000..61f27013d558c
--- /dev/null
+++ b/core/res/res/layout/splash_screen_view.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4109d4c9f6f9c..93c9a8e6b1c99 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1818,6 +1818,8 @@
+
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index b22f358c0781d..a9d66ac945932 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -253,6 +253,11 @@ public class ShellTaskOrganizer extends TaskOrganizer {
mStartingSurfaceDrawer.removeStartingWindow(taskId);
}
+ @Override
+ public void copySplashScreenView(int taskId) {
+ mStartingSurfaceDrawer.copySplashScreenView(taskId);
+ }
+
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
synchronized (mLock) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index f3f2fc3686b63..0e74c642520b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -31,16 +31,13 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
import android.util.Slog;
-import android.view.Gravity;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
+import android.view.Window;
+import android.window.SplashScreenView;
import com.android.internal.R;
import com.android.internal.graphics.palette.Palette;
import com.android.internal.graphics.palette.Quantizer;
import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
-import com.android.internal.policy.PhoneWindow;
import java.util.List;
@@ -83,12 +80,12 @@ class SplashscreenContentDrawer {
return new ColorDrawable(getSystemBGColor());
}
- View makeSplashScreenContentView(PhoneWindow win, Context context, int iconRes,
+ SplashScreenView makeSplashScreenContentView(Window win, Context context, int iconRes,
int splashscreenContentResId) {
updateDensity();
- win.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
// splash screen content will be deprecated after S.
- final View ssc = makeSplashscreenContentDrawable(win, context, splashscreenContentResId);
+ final SplashScreenView ssc =
+ makeSplashscreenContentDrawable(win, context, splashscreenContentResId);
if (ssc != null) {
return ssc;
}
@@ -109,7 +106,7 @@ class SplashscreenContentDrawer {
// TODO (b/173975965) Tracking the performance on improved splash screen.
final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
return builder
- .setPhoneWindow(win)
+ .setWindow(win)
.setContext(context)
.setThemeDrawable(themeBGDrawable)
.setIconDrawable(iconDrawable).build();
@@ -119,12 +116,12 @@ class SplashscreenContentDrawer {
// materials
private Drawable mThemeBGDrawable;
private Drawable mIconDrawable;
- private PhoneWindow mPhoneWindow;
+ private Window mWindow;
private Context mContext;
// result
private boolean mBuildComplete = false;
- private View mCachedResult;
+ private SplashScreenView mCachedResult;
private int mThemeColor;
private Drawable mFinalIconDrawable;
private float mScale = 1f;
@@ -141,8 +138,8 @@ class SplashscreenContentDrawer {
return this;
}
- StartingWindowViewBuilder setPhoneWindow(PhoneWindow window) {
- mPhoneWindow = window;
+ StartingWindowViewBuilder setWindow(Window window) {
+ mWindow = window;
mBuildComplete = false;
return this;
}
@@ -153,11 +150,11 @@ class SplashscreenContentDrawer {
return this;
}
- View build() {
+ SplashScreenView build() {
if (mBuildComplete) {
return mCachedResult;
}
- if (mPhoneWindow == null || mContext == null) {
+ if (mWindow == null || mContext == null) {
Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!");
return null;
}
@@ -173,7 +170,7 @@ class SplashscreenContentDrawer {
mFinalIconDrawable = mIconDrawable;
}
final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
- mCachedResult = fillViewWithIcon(mPhoneWindow, mContext, iconSize, mFinalIconDrawable);
+ mCachedResult = fillViewWithIcon(mWindow, mContext, iconSize, mFinalIconDrawable);
mBuildComplete = true;
return mCachedResult;
}
@@ -249,25 +246,21 @@ class SplashscreenContentDrawer {
return true;
}
- private View fillViewWithIcon(PhoneWindow win, Context context,
+ private SplashScreenView fillViewWithIcon(Window win, Context context,
int iconSize, Drawable iconDrawable) {
- final StartingSurfaceWindowView surfaceWindowView =
- new StartingSurfaceWindowView(context, iconSize);
- surfaceWindowView.setBackground(new ColorDrawable(mThemeColor));
+ final SplashScreenView.Builder builder = new SplashScreenView.Builder(context);
+ builder.setIconSize(iconSize).setBackgroundColor(mThemeColor);
if (iconDrawable != null) {
- surfaceWindowView.setIconDrawable(iconDrawable);
+ builder.setCenterViewDrawable(iconDrawable);
}
+ final SplashScreenView splashScreenView = builder.build();
if (DEBUG) {
- Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + surfaceWindowView);
+ Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + splashScreenView);
}
- win.setContentView(surfaceWindowView);
- makeSystemUIColorsTransparent(win);
- return surfaceWindowView;
- }
-
- private void makeSystemUIColorsTransparent(PhoneWindow win) {
- win.setStatusBarColor(Color.TRANSPARENT);
- win.setNavigationBarColor(Color.TRANSPARENT);
+ win.setContentView(splashScreenView);
+ splashScreenView.cacheRootWindow(win);
+ splashScreenView.makeSystemUIColorsTransparent();
+ return splashScreenView;
}
}
@@ -298,8 +291,8 @@ class SplashscreenContentDrawer {
return root < 0.1;
}
- private static View makeSplashscreenContentDrawable(PhoneWindow win, Context ctx,
- int splashscreenContentResId) {
+ private static SplashScreenView makeSplashscreenContentDrawable(Window win,
+ Context ctx, int splashscreenContentResId) {
// doesn't support windowSplashscreenContent after S
// TODO add an allowlist to skip some packages if needed
final int targetSdkVersion = ctx.getApplicationInfo().targetSdkVersion;
@@ -316,7 +309,8 @@ class SplashscreenContentDrawer {
if (drawable == null) {
return null;
}
- View view = new View(ctx);
+ SplashScreenView view = new SplashScreenView(ctx);
+ view.setNotCopyable();
view.setBackground(drawable);
win.setContentView(view);
return view;
@@ -532,34 +526,4 @@ class SplashscreenContentDrawer {
}
}
}
-
- private static class StartingSurfaceWindowView extends FrameLayout {
- // TODO animate the icon view
- private final View mIconView;
-
- StartingSurfaceWindowView(Context context, int iconSize) {
- super(context);
-
- final boolean emptyIcon = iconSize == 0;
- if (emptyIcon) {
- mIconView = null;
- } else {
- mIconView = new View(context);
- FrameLayout.LayoutParams params =
- new FrameLayout.LayoutParams(iconSize, iconSize);
- params.gravity = Gravity.CENTER;
- addView(mIconView, params);
- }
- setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
- }
-
- // TODO support animatable icon
- void setIconDrawable(Drawable icon) {
- if (mIconView != null) {
- mIconView.setBackground(icon);
- }
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index f3749220d4e1a..03935cfd4259d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -43,6 +43,8 @@ import android.util.SparseArray;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
+import android.window.SplashScreenView;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.StartingWindowInfo;
import android.window.TaskOrganizer;
import android.window.TaskSnapshot;
@@ -63,7 +65,6 @@ import java.util.function.Consumer;
* class to remove the starting window of the Task.
* @hide
*/
-
public class StartingSurfaceDrawer {
static final String TAG = StartingSurfaceDrawer.class.getSimpleName();
static final boolean DEBUG_SPLASH_SCREEN = false;
@@ -352,9 +353,10 @@ public class StartingSurfaceDrawer {
}
params.setTitle("Splash Screen " + activityInfo.packageName);
- final View contentView = mSplashscreenContentDrawer.makeSplashScreenContentView(win,
- context, iconRes, splashscreenContentResId[0]);
- if (contentView == null) {
+ final SplashScreenView splashScreenView =
+ mSplashscreenContentDrawer.makeSplashScreenContentView(win, context, iconRes,
+ splashscreenContentResId[0]);
+ if (splashScreenView == null) {
Slog.w(TAG, "Adding splash screen window for " + activityInfo.packageName + " failed!");
return;
}
@@ -366,7 +368,7 @@ public class StartingSurfaceDrawer {
+ activityInfo.packageName + " / " + appToken + ": " + view);
}
final WindowManager wm = context.getSystemService(WindowManager.class);
- postAddWindow(taskInfo.taskId, appToken, view, wm, params);
+ postAddWindow(taskInfo.taskId, appToken, view, wm, params, splashScreenView);
}
/**
@@ -379,7 +381,7 @@ public class StartingSurfaceDrawer {
snapshot, mMainExecutor, () -> removeWindowSynced(taskId) /* clearWindow */);
mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
final StartingWindowRecord tView =
- new StartingWindowRecord(null/* decorView */, surface);
+ new StartingWindowRecord(null/* decorView */, surface, null /* splashScreenView */);
mStartingWindowRecords.put(taskId, tView);
}
@@ -393,37 +395,59 @@ public class StartingSurfaceDrawer {
removeWindowSynced(taskId);
}
- protected void postAddWindow(int taskId, IBinder appToken,
- View view, WindowManager wm, WindowManager.LayoutParams params) {
- boolean shouldSaveView = true;
- try {
- wm.addView(view, params);
- } catch (WindowManager.BadTokenException e) {
- // ignore
- Slog.w(TAG, appToken + " already running, starting window not displayed. "
- + e.getMessage());
- shouldSaveView = false;
- } catch (RuntimeException e) {
- // don't crash if something else bad happens, for example a
- // failure loading resources because we are loading from an app
- // on external storage that has been unmounted.
- Slog.w(TAG, appToken + " failed creating starting window", e);
- shouldSaveView = false;
- } finally {
- if (view != null && view.getParent() == null) {
- Slog.w(TAG, "view not successfully added to wm, removing view");
- wm.removeViewImmediate(view);
- shouldSaveView = false;
- }
+ /**
+ * Called when the Task wants to copy the splash screen.
+ * @param taskId
+ */
+ public void copySplashScreenView(int taskId) {
+ final StartingWindowRecord preView = mStartingWindowRecords.get(taskId);
+ final SplashScreenViewParcelable parcelable;
+ if (preView != null && preView.mContentView != null
+ && preView.mContentView.isCopyable()) {
+ parcelable = new SplashScreenViewParcelable(preView.mContentView);
+ } else {
+ parcelable = null;
}
+ if (DEBUG_SPLASH_SCREEN) {
+ Slog.v(TAG, "Copying splash screen window view for task: " + taskId
+ + " parcelable? " + parcelable);
+ }
+ ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
+ }
- if (shouldSaveView) {
- removeWindowSynced(taskId);
- mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
- final StartingWindowRecord tView =
- new StartingWindowRecord(view, null /* TaskSnapshotWindow */);
- mStartingWindowRecords.put(taskId, tView);
- }
+ protected void postAddWindow(int taskId, IBinder appToken,
+ View view, WindowManager wm, WindowManager.LayoutParams params,
+ SplashScreenView splashScreenView) {
+ mMainExecutor.execute(() -> {
+ boolean shouldSaveView = true;
+ try {
+ wm.addView(view, params);
+ } catch (WindowManager.BadTokenException e) {
+ // ignore
+ Slog.w(TAG, appToken + " already running, starting window not displayed. "
+ + e.getMessage());
+ shouldSaveView = false;
+ } catch (RuntimeException e) {
+ // don't crash if something else bad happens, for example a
+ // failure loading resources because we are loading from an app
+ // on external storage that has been unmounted.
+ Slog.w(TAG, appToken + " failed creating starting window", e);
+ shouldSaveView = false;
+ } finally {
+ if (view != null && view.getParent() == null) {
+ Slog.w(TAG, "view not successfully added to wm, removing view");
+ wm.removeViewImmediate(view);
+ shouldSaveView = false;
+ }
+ }
+ if (shouldSaveView) {
+ removeWindowSynced(taskId);
+ mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
+ final StartingWindowRecord tView = new StartingWindowRecord(view,
+ null /* TaskSnapshotWindow */, splashScreenView);
+ mStartingWindowRecords.put(taskId, tView);
+ }
+ });
}
protected void removeWindowSynced(int taskId) {
@@ -459,10 +483,13 @@ public class StartingSurfaceDrawer {
private static class StartingWindowRecord {
private final View mDecorView;
private final TaskSnapshotWindow mTaskSnapshotWindow;
+ private final SplashScreenView mContentView;
- StartingWindowRecord(View decorView, TaskSnapshotWindow taskSnapshotWindow) {
+ StartingWindowRecord(View decorView, TaskSnapshotWindow taskSnapshotWindow,
+ SplashScreenView splashScreenView) {
mDecorView = decorView;
mTaskSnapshotWindow = taskSnapshotWindow;
+ mContentView = splashScreenView;
}
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index c9537afa37ef3..de7d6c74bb065 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -40,6 +40,7 @@ import android.testing.TestableContext;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowMetrics;
+import android.window.SplashScreenView;
import android.window.StartingWindowInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -79,7 +80,8 @@ public class StartingSurfaceDrawerTests {
@Override
protected void postAddWindow(int taskId, IBinder appToken,
- View view, WindowManager wm, WindowManager.LayoutParams params) {
+ View view, WindowManager wm, WindowManager.LayoutParams params,
+ SplashScreenView splashScreenView) {
// listen for addView
mAddWindowForTask = taskId;
mViewThemeResId = view.getContext().getThemeResId();
@@ -125,7 +127,8 @@ public class StartingSurfaceDrawerTests {
createWindowInfo(taskId, android.R.style.Theme);
mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder);
waitHandlerIdle(mainLoop);
- verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
+ verify(mStartingSurfaceDrawer).postAddWindow(
+ eq(taskId), eq(mBinder), any(), any(), any(), any());
assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId);
@@ -142,7 +145,8 @@ public class StartingSurfaceDrawerTests {
createWindowInfo(taskId, 0);
mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder);
waitHandlerIdle(mainLoop);
- verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
+ verify(mStartingSurfaceDrawer).postAddWindow(
+ eq(taskId), eq(mBinder), any(), any(), any(), any());
assertNotEquals(mStartingSurfaceDrawer.mViewThemeResId, 0);
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 771b712cf4802..c63a0f093dea1 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -134,10 +134,10 @@ class ActivityClientController extends IActivityClientController.Stub {
}
@Override
- public void activityResumed(IBinder token) {
+ public void activityResumed(IBinder token, boolean handleSplashScreenExit) {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
- ActivityRecord.activityResumedLocked(token);
+ ActivityRecord.activityResumedLocked(token, handleSplashScreenExit);
}
Binder.restoreCallingIdentity(origId);
}
@@ -691,6 +691,18 @@ class ActivityClientController extends IActivityClientController.Stub {
}
}
+ /**
+ * Splash screen view is attached to activity.
+ */
+ @Override
+ public void splashScreenAttached(IBinder token) {
+ final long origId = Binder.clearCallingIdentity();
+ synchronized (mGlobalLock) {
+ ActivityRecord.splashScreenAttachedLocked(token);
+ }
+ Binder.restoreCallingIdentity(origId);
+ }
+
/**
* Checks the state of the system and the activity associated with the given {@param token} to
* verify that picture-in-picture is supported for that activity.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 68a2c5d5233cb..3ca6039a0f733 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -42,6 +42,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.servertransaction.TransferSplashScreenViewStateItem.ATTACH_TO;
+import static android.app.servertransaction.TransferSplashScreenViewStateItem.HANDOVER_TO;
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_HOME;
import static android.content.Intent.CATEGORY_LAUNCHER;
@@ -243,6 +245,7 @@ import android.app.servertransaction.ResumeActivityItem;
import android.app.servertransaction.StartActivityItem;
import android.app.servertransaction.StopActivityItem;
import android.app.servertransaction.TopResumedActivityChangeItem;
+import android.app.servertransaction.TransferSplashScreenViewStateItem;
import android.app.usage.UsageEvents.Event;
import android.content.ComponentName;
import android.content.Intent;
@@ -300,6 +303,7 @@ import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.TransitionOldType;
import android.view.animation.Animation;
import android.window.IRemoteTransition;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
@@ -669,6 +673,30 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
boolean startingDisplayed;
boolean startingMoved;
+ boolean mHandleExitSplashScreen;
+ @TransferSplashScreenState int mTransferringSplashScreenState =
+ TRANSFER_SPLASH_SCREEN_IDLE;
+
+ // Idle, can be triggered to do transfer if needed.
+ static final int TRANSFER_SPLASH_SCREEN_IDLE = 0;
+ // requesting a copy from shell.
+ static final int TRANSFER_SPLASH_SCREEN_COPYING = 1;
+ // attach the splash screen view to activity.
+ static final int TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT = 2;
+ // client has taken over splash screen view.
+ static final int TRANSFER_SPLASH_SCREEN_FINISH = 3;
+
+ @IntDef(prefix = { "TRANSFER_SPLASH_SCREEN_" }, value = {
+ TRANSFER_SPLASH_SCREEN_IDLE,
+ TRANSFER_SPLASH_SCREEN_COPYING,
+ TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT,
+ TRANSFER_SPLASH_SCREEN_FINISH,
+ })
+ @interface TransferSplashScreenState {}
+
+ // How long we wait until giving up transfer splash screen.
+ private static final int TRANSFER_SPLASH_SCREEN_TIMEOUT = 2000;
+
// TODO: Have a WindowContainer state for tracking exiting/deferred removal.
boolean mIsExiting;
// Force an app transition to be ran in the case the visibility of the app did not change.
@@ -2031,7 +2059,118 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return snapshot.getRotation() == targetRotation;
}
+ /**
+ * See {@link SplashScreen#setOnExitAnimationListener}.
+ */
+ void setCustomizeSplashScreenExitAnimation(boolean enable) {
+ if (mHandleExitSplashScreen == enable) {
+ return;
+ }
+ mHandleExitSplashScreen = enable;
+ }
+
+ private final Runnable mTransferSplashScreenTimeoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mAtmService.mGlobalLock) {
+ Slog.w(TAG, "Activity transferring splash screen timeout for "
+ + ActivityRecord.this + " state " + mTransferringSplashScreenState);
+ if (isTransferringSplashScreen()) {
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+ // TODO show default exit splash screen animation
+ removeStartingWindow();
+ }
+ }
+ }
+ };
+
+ private void scheduleTransferSplashScreenTimeout() {
+ mAtmService.mH.postDelayed(mTransferSplashScreenTimeoutRunnable,
+ TRANSFER_SPLASH_SCREEN_TIMEOUT);
+ }
+
+ private void removeTransferSplashScreenTimeout() {
+ mAtmService.mH.removeCallbacks(mTransferSplashScreenTimeoutRunnable);
+ }
+
+ private boolean transferSplashScreenIfNeeded() {
+ if (!mHandleExitSplashScreen || mStartingSurface == null || mStartingWindow == null
+ || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH) {
+ return false;
+ }
+ if (isTransferringSplashScreen()) {
+ return true;
+ }
+ requestCopySplashScreen();
+ return isTransferringSplashScreen();
+ }
+
+ private boolean isTransferringSplashScreen() {
+ return mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT
+ || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_COPYING;
+ }
+
+ private void requestCopySplashScreen() {
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_COPYING;
+ if (!mAtmService.mTaskOrganizerController.copySplashScreenView(getTask())) {
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+ removeStartingWindow();
+ }
+ scheduleTransferSplashScreenTimeout();
+ }
+
+ /**
+ * Receive the splash screen data from shell, sending to client.
+ * @param parcelable The data to reconstruct the splash screen view, null mean unable to copy.
+ */
+ void onCopySplashScreenFinish(SplashScreenViewParcelable parcelable) {
+ removeTransferSplashScreenTimeout();
+ // unable to copy from shell, maybe it's not a splash screen. or something went wrong.
+ // either way, abort and reset the sequence.
+ if (parcelable == null
+ || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING) {
+ if (parcelable != null) {
+ parcelable.clearIfNeeded();
+ }
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+ removeStartingWindow();
+ return;
+ }
+ // schedule attach splashScreen to client
+ try {
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ TransferSplashScreenViewStateItem.obtain(ATTACH_TO, parcelable));
+ scheduleTransferSplashScreenTimeout();
+ } catch (Exception e) {
+ Slog.w(TAG, "onCopySplashScreenComplete fail: " + this);
+ parcelable.clearIfNeeded();
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+ }
+ }
+
+ private void onSplashScreenAttachComplete() {
+ removeTransferSplashScreenTimeout();
+ // Client has draw the splash screen, so we can remove the starting window.
+ if (mStartingWindow != null) {
+ mStartingWindow.hide(false, false);
+ }
+ try {
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ TransferSplashScreenViewStateItem.obtain(HANDOVER_TO, null));
+ } catch (Exception e) {
+ Slog.w(TAG, "onSplashScreenAttachComplete fail: " + this);
+ }
+ // no matter what, remove the starting window.
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+ removeStartingWindow();
+ }
+
void removeStartingWindow() {
+ if (transferSplashScreenIfNeeded()) {
+ return;
+ }
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
if (mStartingWindow == null) {
if (mStartingData != null) {
// Starting window has not been added yet, but it is scheduled to be added.
@@ -5092,7 +5231,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
- static void activityResumedLocked(IBinder token) {
+ static void activityResumedLocked(IBinder token, boolean handleSplashScreenExit) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
ProtoLog.i(WM_DEBUG_STATES, "Resumed activity; dropping state of: %s", r);
if (r == null) {
@@ -5100,12 +5239,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// been removed (e.g. destroy timeout), so the token could be null.
return;
}
+ r.setCustomizeSplashScreenExitAnimation(handleSplashScreenExit);
r.setSavedState(null /* savedState */);
r.mDisplayContent.handleActivitySizeCompatModeIfNeeded(r);
r.mDisplayContent.mUnknownAppVisibilityController.notifyAppResumedFinished(r);
}
+ static void splashScreenAttachedLocked(IBinder token) {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ if (r == null) {
+ Slog.w(TAG, "splashScreenTransferredLocked cannot find activity");
+ return;
+ }
+ r.onSplashScreenAttachComplete();
+ }
+
/**
* Once we know that we have asked an application to put an activity in the resumed state
* (either by launching it or explicitly telling it), this function updates the rest of our
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f16a646d00a16..0e1941ace6249 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -224,6 +224,7 @@ import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
import android.window.IWindowOrganizerController;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.TaskSnapshot;
import android.window.WindowContainerTransaction;
@@ -3248,6 +3249,30 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
}
+ /**
+ * A splash screen view has copied, pass it to an activity.
+ *
+ * @param taskId Id of task to handle the material to reconstruct the view.
+ * @param parcelable Used to reconstruct the view, null means the surface is un-copyable.
+ * @hide
+ */
+ @Override
+ public void onSplashScreenViewCopyFinished(int taskId, SplashScreenViewParcelable parcelable)
+ throws RemoteException {
+ mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS,
+ "copySplashScreenViewFinish()");
+ synchronized (mGlobalLock) {
+ final Task task = mRootWindowContainer.anyTaskForId(taskId,
+ MATCH_ATTACHED_TASK_ONLY);
+ if (task != null) {
+ final ActivityRecord r = task.getTopWaitSplashScreenActivity();
+ if (r != null) {
+ r.onCopySplashScreenFinish(parcelable);
+ }
+ }
+ }
+ }
+
/**
* Puts the given activity in picture in picture mode if possible.
*
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9c8a997ec0987..e39ae9ae0babd 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -82,6 +82,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMA
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN;
+import static com.android.server.wm.ActivityRecord.TRANSFER_SPLASH_SCREEN_COPYING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
@@ -3881,6 +3882,13 @@ class Task extends WindowContainer {
});
}
+ ActivityRecord getTopWaitSplashScreenActivity() {
+ return getActivity((r) -> {
+ return r.mHandleExitSplashScreen
+ && r.mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_COPYING;
+ });
+ }
+
boolean isTopActivityFocusable() {
final ActivityRecord r = topRunningActivity();
return r != null ? r.isFocusable()
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 9fac3f003f707..96d7ae3c5a5df 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -138,6 +138,16 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
});
}
+ void copySplashScreenView(Task task) {
+ mDeferTaskOrgCallbacksConsumer.accept(() -> {
+ try {
+ mTaskOrganizer.copySplashScreenView(task.mTaskId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending copyStartingWindowView callback", e);
+ }
+ });
+ }
+
SurfaceControl prepareLeash(Task task, boolean visible, String reason) {
SurfaceControl outSurfaceControl = new SurfaceControl(task.getSurfaceControl(), reason);
if (!task.mCreatedByOrganizer && !visible) {
@@ -240,6 +250,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
mOrganizer.removeStartingWindow(t);
}
+ void copySplashScreenView(Task t) {
+ mOrganizer.copySplashScreenView(t);
+ }
+
/**
* Register this task with this state, but doesn't trigger the task appeared callback to
* the organizer.
@@ -486,6 +500,17 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
state.removeStartingWindow(task);
}
+ boolean copySplashScreenView(Task task) {
+ final Task rootTask = task.getRootTask();
+ if (rootTask == null || rootTask.mTaskOrganizer == null) {
+ return false;
+ }
+ final TaskOrganizerState state =
+ mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
+ state.copySplashScreenView(task);
+ return true;
+ }
+
void onTaskAppeared(ITaskOrganizer organizer, Task task) {
final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
if (state != null && state.addTask(task)) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 72b84396e9858..aa1110cd55a76 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -719,7 +719,7 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = createActivityWithTask();
assertTrue(activity.hasSavedState());
- ActivityRecord.activityResumedLocked(activity.appToken);
+ ActivityRecord.activityResumedLocked(activity.appToken, false /* handleSplashScreenExit */);
assertFalse(activity.hasSavedState());
assertNull(activity.getSavedState());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 77fca3d2fdeb2..9c16143935547 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -548,6 +548,9 @@ public class WindowOrganizerTests extends WindowTestsBase {
@Override
public void removeStartingWindow(int taskId) { }
+ @Override
+ public void copySplashScreenView(int taskId) { }
+
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { }
@@ -609,10 +612,10 @@ public class WindowOrganizerTests extends WindowTestsBase {
public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
}
-
@Override
public void removeStartingWindow(int taskId) { }
-
+ @Override
+ public void copySplashScreenView(int taskId) { }
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { }
@@ -685,7 +688,8 @@ public class WindowOrganizerTests extends WindowTestsBase {
@Override
public void removeStartingWindow(int taskId) { }
-
+ @Override
+ public void copySplashScreenView(int taskId) { }
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { }
@@ -829,6 +833,8 @@ public class WindowOrganizerTests extends WindowTestsBase {
@Override
public void removeStartingWindow(int taskId) { }
@Override
+ public void copySplashScreenView(int taskId) { }
+ @Override
public void onTaskAppeared(RunningTaskInfo info, SurfaceControl leash) {
mInfo = info;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 83b30a9747d74..c13d6b19bf1db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1144,6 +1144,9 @@ class WindowTestsBase extends SystemServiceTestsBase {
public void removeStartingWindow(int taskId) {
}
@Override
+ public void copySplashScreenView(int taskId) {
+ }
+ @Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) {
}
@Override