diff options
31 files changed, 1265 insertions, 116 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index e264f1ddddb6..ffe14c3f1954 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 1be0fba570ab..cefd1b153268 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<android.app.ActivityManager.RunningTaskInfo> getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 4728f11a402d..992d054737b9 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() { /** @@ -1603,6 +1609,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 * <code>persistAcrossReboots</code>. diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index 401f8cc13bad..e3b5e9a32324 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 70fa4445479b..233f737b8e0f 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; @@ -243,6 +245,22 @@ public class ActivityTaskManager { } /** + * 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 bb6a774cbee2..3d9f6123963f 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<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>(); + 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) { @@ -3955,6 +3970,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 * onPictureInPictureRequested to enter picture-in-picture. @@ -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 0e1c827145d2..cf5fd148c030 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 9d3286fa271c..bb743b89e00f 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 38a3e70b3742..542f754ce364 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<String> 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 d451599cc7b0..e6fdc006615a 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 000000000000..5374984d31d0 --- /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<TransferSplashScreenViewStateItem> CREATOR = + new Creator<TransferSplashScreenViewStateItem>() { + 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 6b13a290b20e..228ee3c76b14 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 88b2257a55b1..8f541d0bd194 100644 --- a/core/java/android/window/ITaskOrganizer.aidl +++ b/core/java/android/window/ITaskOrganizer.aidl @@ -42,6 +42,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 * be hidden so it is up to the organizer to show this task. diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java new file mode 100644 index 000000000000..4b88a9bc39de --- /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. + * <p> + * Each splash screen instance is bound to a particular {@link Activity}. + * To obtain a {@link SplashScreen} for an Activity, use + * <code>Activity.getSplashScreen()</code> to get the SplashScreen.</p> + */ +public interface SplashScreen { + /** + * <p>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.</p> + * + * <p> Note that this method must be called before splash screen leave, so it only takes effect + * during or before {@link Activity#onResume}.</p> + * + * @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.</p> + * + * <p>If {@link SplashScreenView#remove} is not called after 5000ms, the method will be + * automatically called and the splash screen removed.</p> + * + * <p>This method is never invoked if your activity sets + * {@link #setOnExitAnimationListener} to <code>null</code>.. + * + * @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<SplashScreenImpl> mImpls = new ArrayList<>(); + + private SplashScreenManagerGlobal() { + ActivityThread.currentActivityThread().registerSplashScreenManager(this); + } + + public static SplashScreenManagerGlobal getInstance() { + return sInstance.get(); + } + + private static final Singleton<SplashScreenManagerGlobal> sInstance = + new Singleton<SplashScreenManagerGlobal>() { + @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 000000000000..cc7ac1edce80 --- /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 000000000000..b8d3fc2eda05 --- /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; + +/** + * <p>The view which allows an activity to customize its splash screen exit animation.</p> + * + * <p>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.</p> + * + * <p>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}.</p> + * + * @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; + } + + /** + * <p>Remove this view and release its resource. </p> + * <p><strong>Do not</strong> invoke this method from a drawing method + * ({@link #onDraw(android.graphics.Canvas)} for instance).</p> + */ + 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<SplashScreenViewParcelable> CREATOR = + new Parcelable.Creator<SplashScreenViewParcelable>() { + 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 cdb4762a4f0a..217ade82b336 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -105,6 +105,12 @@ public class TaskOrganizer extends WindowOrganizer { 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 * to show this task. @@ -223,6 +229,11 @@ public class TaskOrganizer extends WindowOrganizer { } @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 000000000000..61f27013d558 --- /dev/null +++ b/core/res/res/layout/splash_screen_view.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<android.window.SplashScreenView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:orientation="vertical"> + + <View android:id="@+id/splashscreen_icon_view" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center"/> + +</android.window.SplashScreenView>
\ No newline at end of file diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 4109d4c9f6f9..93c9a8e6b1c9 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1818,6 +1818,8 @@ <java-symbol type="string" name="forward_intent_to_owner" /> <java-symbol type="string" name="forward_intent_to_work" /> <java-symbol type="dimen" name="cross_profile_apps_thumbnail_size" /> + <java-symbol type="layout" name="splash_screen_view" /> + <java-symbol type="id" name="splashscreen_icon_view" /> <!-- From services --> <java-symbol type="anim" name="screen_rotate_0_enter" /> 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 b22f358c0781..a9d66ac94593 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -254,6 +254,11 @@ public class ShellTaskOrganizer extends TaskOrganizer { } @Override + public void copySplashScreenView(int taskId) { + mStartingSurfaceDrawer.copySplashScreenView(taskId); + } + + @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { synchronized (mLock) { onTaskAppeared(new TaskAppearedInfo(taskInfo, leash)); 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 f3f2fc3686b6..0e74c642520b 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 f3749220d4e1..03935cfd4259 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); } + /** + * 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); + } + 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); + 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 */); - mStartingWindowRecords.put(taskId, tView); - } + 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 c9537afa37ef..de7d6c74bb06 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 771b712cf480..c63a0f093dea 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); } @@ -692,6 +692,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 68a2c5d5233c..3ca6039a0f73 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 f16a646d00a1..0e1941ace624 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; @@ -3249,6 +3250,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. * * @return true if the activity is now in picture-in-picture mode, or false if it could not diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9c8a997ec098..e39ae9ae0bab 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<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 9fac3f003f70..96d7ae3c5a5d 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 72b84396e985..aa1110cd55a7 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 77fca3d2fdeb..9c1614393554 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -549,6 +549,9 @@ public class WindowOrganizerTests extends WindowTestsBase { public void removeStartingWindow(int taskId) { } @Override + public void copySplashScreenView(int taskId) { } + + @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { } @Override @@ -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 83b30a9747d7..c13d6b19bf1d 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 |