summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt27
-rw-r--r--core/api/test-current.txt5
-rw-r--r--core/java/android/app/Activity.java23
-rw-r--r--core/java/android/app/ActivityClient.java15
-rw-r--r--core/java/android/app/ActivityOptions.java19
-rw-r--r--core/java/android/app/ActivityTaskManager.java18
-rw-r--r--core/java/android/app/ActivityThread.java120
-rw-r--r--core/java/android/app/ClientTransactionHandler.java11
-rw-r--r--core/java/android/app/IActivityClientController.aidl5
-rw-r--r--core/java/android/app/IActivityTaskManager.aidl7
-rw-r--r--core/java/android/app/IUiModeManager.aidl10
-rw-r--r--core/java/android/app/UiModeManager.java44
-rw-r--r--core/java/android/app/servertransaction/ResumeActivityItem.java2
-rw-r--r--core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java105
-rw-r--r--core/java/android/content/pm/AppSearchShortcutInfo.java2
-rw-r--r--core/java/android/content/pm/ShortcutInfo.java37
-rw-r--r--core/java/android/content/pm/ShortcutServiceInternal.java7
-rw-r--r--core/java/android/view/ViewRootImpl.java14
-rw-r--r--core/java/android/window/ITaskOrganizer.aidl5
-rw-r--r--core/java/android/window/SplashScreen.java188
-rw-r--r--core/java/android/window/SplashScreenView.aidl20
-rw-r--r--core/java/android/window/SplashScreenView.java510
-rw-r--r--core/java/android/window/StartingWindowInfo.java11
-rw-r--r--core/java/android/window/TaskOrganizer.java11
-rw-r--r--core/res/res/layout/splash_screen_view.xml34
-rw-r--r--core/res/res/values/attrs.xml18
-rw-r--r--core/res/res/values/public.xml5
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--core/res/res/values/themes.xml3
-rw-r--r--data/etc/services.core.protolog.json12
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml3
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java187
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java120
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java10
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java37
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java12
-rw-r--r--services/core/java/com/android/server/pm/ShortcutPackage.java8
-rw-r--r--services/core/java/com/android/server/pm/ShortcutParser.java10
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java26
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java16
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java294
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java22
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java83
-rw-r--r--services/core/java/com/android/server/wm/ConfigurationContainer.java22
-rw-r--r--services/core/java/com/android/server/wm/PackageConfigPersister.java380
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java2
-rw-r--r--services/core/java/com/android/server/wm/StartingSurfaceController.java5
-rw-r--r--services/core/java/com/android/server/wm/Task.java24
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java38
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java8
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java3
58 files changed, 2426 insertions, 229 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index cc5399de24cc..9ef3eb462552 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1295,6 +1295,7 @@ package android {
field public static final int spinnerMode = 16843505; // 0x10102f1
field public static final int spinnerStyle = 16842881; // 0x1010081
field public static final int spinnersShown = 16843595; // 0x101034b
+ field public static final int splashScreenTheme = 16844336; // 0x1010630
field public static final int splitMotionEvents = 16843503; // 0x10102ef
field public static final int splitName = 16844105; // 0x1010549
field public static final int splitTrack = 16843852; // 0x101044c
@@ -1655,6 +1656,10 @@ package android {
field public static final int windowShowAnimation = 16842934; // 0x10100b6
field public static final int windowShowWallpaper = 16843410; // 0x1010292
field public static final int windowSoftInputMode = 16843307; // 0x101022b
+ field public static final int windowSplashScreenAnimatedIcon = 16844333; // 0x101062d
+ field public static final int windowSplashScreenAnimationDuration = 16844334; // 0x101062e
+ field public static final int windowSplashScreenBackground = 16844332; // 0x101062c
+ field public static final int windowSplashScreenBrandingImage = 16844335; // 0x101062f
field public static final int windowSplashscreenContent = 16844132; // 0x1010564
field @Deprecated public static final int windowSwipeToDismiss = 16843763; // 0x10103f3
field public static final int windowTitleBackgroundStyle = 16842844; // 0x101005c
@@ -3865,6 +3870,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();
@@ -6653,6 +6659,7 @@ package android.app {
method @NonNull public java.time.LocalTime getCustomNightModeEnd();
method @NonNull public java.time.LocalTime getCustomNightModeStart();
method public int getNightMode();
+ method public void setApplicationNightMode(int);
method public void setCustomNightModeEnd(@NonNull java.time.LocalTime);
method public void setCustomNightModeStart(@NonNull java.time.LocalTime);
method public void setNightMode(int);
@@ -12779,6 +12786,7 @@ package android.content.pm {
method @NonNull public android.content.pm.ShortcutInfo.Builder setPersons(@NonNull android.app.Person[]);
method @NonNull public android.content.pm.ShortcutInfo.Builder setRank(int);
method @NonNull public android.content.pm.ShortcutInfo.Builder setShortLabel(@NonNull CharSequence);
+ method @NonNull public android.content.pm.ShortcutInfo.Builder setStartingTheme(int);
}
public class ShortcutManager {
@@ -55824,6 +55832,25 @@ 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 public long getIconAnimationDurationMillis();
+ method public long getIconAnimationStartMillis();
+ 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..29be5c6e4681 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2729,6 +2729,10 @@ package android.window {
field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2
}
+ public final class SplashScreenView extends android.widget.FrameLayout {
+ method @Nullable public android.view.View getBrandingView();
+ }
+
public final class StartingWindowInfo implements android.os.Parcelable {
ctor public StartingWindowInfo();
method public int describeContents();
@@ -2748,6 +2752,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/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 28da1c3a3eb7..73cc13c82bcb 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -160,6 +160,12 @@ public class ActivityOptions {
public static final String KEY_ANIM_START_LISTENER = "android:activity.animStartListener";
/**
+ * Specific a theme for a splash screen window.
+ * @hide
+ */
+ public static final String KEY_SPLASH_SCREEN_THEME = "android.activity.splashScreenTheme";
+
+ /**
* Callback for when the last frame of the animation is played.
* @hide
*/
@@ -398,6 +404,7 @@ public class ActivityOptions {
private IBinder mLaunchCookie;
private IRemoteTransition mRemoteTransition;
private boolean mOverrideTaskTransition;
+ private int mSplashScreenThemeResId;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -1147,6 +1154,7 @@ public class ActivityOptions {
mRemoteTransition = IRemoteTransition.Stub.asInterface(opts.getBinder(
KEY_REMOTE_TRANSITION));
mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION);
+ mSplashScreenThemeResId = opts.getInt(KEY_SPLASH_SCREEN_THEME);
}
/**
@@ -1333,6 +1341,14 @@ public class ActivityOptions {
}
/**
+ * Gets whether the activity want to be launched as other theme for the splash screen.
+ * @hide
+ */
+ public int getSplashScreenThemeResId() {
+ return mSplashScreenThemeResId;
+ }
+
+ /**
* Sets whether the activity is to be launched into LockTask mode.
*
* Use this option to start an activity in LockTask mode. Note that only apps permitted by
@@ -1838,6 +1854,9 @@ public class ActivityOptions {
if (mOverrideTaskTransition) {
b.putBoolean(KEY_OVERRIDE_TASK_TRANSITION, mOverrideTaskTransition);
}
+ if (mSplashScreenThemeResId != 0) {
+ b.putInt(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResId);
+ }
return b;
}
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/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 0ba5beccbf32..f71eebdc66ec 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -62,6 +62,16 @@ interface IUiModeManager {
int getNightMode();
/**
+ * Sets the dark mode for the given application. This setting is persisted and will override the
+ * system configuration for this application.
+ * 1 - notnight mode
+ * 2 - night mode
+ * 3 - automatic mode switching
+ * @throws RemoteException
+ */
+ void setApplicationNightMode(in int mode);
+
+ /**
* Tells if UI mode is locked or not.
*/
boolean isUiModeLocked();
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index e1c262caf44f..9b99ab8e31cb 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -477,11 +477,13 @@ public class UiModeManager {
* Changes to night mode take effect globally and will result in a configuration change
* (and potentially an Activity lifecycle event) being applied to all running apps.
* Developers interested in an app-local implementation of night mode should consider using
- * {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} to manage the
- * -night qualifier locally.
+ * {@link #setApplicationNightMode(int)} to set and persist the -night qualifier locally or
+ * {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} for the
+ * backward compatible implementation.
*
* @param mode the night mode to set
* @see #getNightMode()
+ * @see #setApplicationNightMode(int)
*/
public void setNightMode(@NightMode int mode) {
if (mService != null) {
@@ -494,6 +496,44 @@ public class UiModeManager {
}
/**
+ * Sets and persist the night mode for this application.
+ * <p>
+ * The mode can be one of:
+ * <ul>
+ * <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into
+ * {@code notnight} mode</li>
+ * <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into
+ * {@code night} mode</li>
+ * <li><em>{@link #MODE_NIGHT_CUSTOM}</em> automatically switches between
+ * {@code night} and {@code notnight} based on the custom time set (or default)</li>
+ * <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between
+ * {@code night} and {@code notnight} based on the device's current
+ * location and certain other sensors</li>
+ * </ul>
+ * <p>
+ * Changes to night mode take effect locally and will result in a configuration change
+ * (and potentially an Activity lifecycle event) being applied to this application. The mode
+ * is persisted for this application until it is either modified by the application, the
+ * user clears the data for the application, or this application is uninstalled.
+ * <p>
+ * Developers interested in a non-persistent app-local implementation of night mode should
+ * consider using {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)}
+ * to manage the -night qualifier locally.
+ *
+ * @param mode the night mode to set
+ * @see #setNightMode(int)
+ */
+ public void setApplicationNightMode(@NightMode int mode) {
+ if (mService != null) {
+ try {
+ mService.setApplicationNightMode(mode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Returns the currently configured night mode.
* <p>
* May be one of:
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/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index 85549d854c6d..ebe202b2a3fa 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -273,7 +273,7 @@ public class AppSearchShortcutInfo extends GenericDocument {
text, 0, null, disabledMessage, 0, null,
categoriesSet, intents, rank, extras,
getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri,
- disabledReason, persons, locusId);
+ disabledReason, persons, locusId, 0);
}
/** @hide */
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 522f4ca88519..ce0547f5d0f4 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -434,6 +434,8 @@ public final class ShortcutInfo implements Parcelable {
private int mDisabledReason;
+ private int mStartingThemeResId;
+
private ShortcutInfo(Builder b) {
mUserId = b.mContext.getUserId();
@@ -462,6 +464,7 @@ public final class ShortcutInfo implements Parcelable {
mLocusId = b.mLocusId;
updateTimestamp();
+ mStartingThemeResId = b.mStartingThemeResId;
}
/**
@@ -608,6 +611,7 @@ public final class ShortcutInfo implements Parcelable {
// Set this bit.
mFlags |= FLAG_KEY_FIELDS_ONLY;
}
+ mStartingThemeResId = source.mStartingThemeResId;
}
/**
@@ -931,6 +935,9 @@ public final class ShortcutInfo implements Parcelable {
if (source.mLocusId != null) {
mLocusId = source.mLocusId;
}
+ if (source.mStartingThemeResId != 0) {
+ mStartingThemeResId = source.mStartingThemeResId;
+ }
}
/**
@@ -1000,6 +1007,8 @@ public final class ShortcutInfo implements Parcelable {
private LocusId mLocusId;
+ private int mStartingThemeResId;
+
/**
* Old style constructor.
* @hide
@@ -1102,6 +1111,15 @@ public final class ShortcutInfo implements Parcelable {
}
/**
+ * Sets a theme resource id for the splash screen.
+ */
+ @NonNull
+ public Builder setStartingTheme(int themeResId) {
+ mStartingThemeResId = themeResId;
+ return this;
+ }
+
+ /**
* @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests
* use it.)
*/
@@ -1420,6 +1438,14 @@ public final class ShortcutInfo implements Parcelable {
return mIcon;
}
+ /**
+ * Returns the theme resource id used for the splash screen.
+ * @hide
+ */
+ public int getStartingThemeResId() {
+ return mStartingThemeResId;
+ }
+
/** @hide -- old signature, the internal code still uses it. */
@Nullable
@Deprecated
@@ -2138,6 +2164,7 @@ public final class ShortcutInfo implements Parcelable {
mPersons = source.readParcelableArray(cl, Person.class);
mLocusId = source.readParcelable(cl);
mIconUri = source.readString8();
+ mStartingThemeResId = source.readInt();
}
@Override
@@ -2189,6 +2216,7 @@ public final class ShortcutInfo implements Parcelable {
dest.writeParcelableArray(mPersons, flags);
dest.writeParcelable(mLocusId, flags);
dest.writeString8(mIconUri);
+ dest.writeInt(mStartingThemeResId);
}
public static final @NonNull Creator<ShortcutInfo> CREATOR =
@@ -2345,6 +2373,12 @@ public final class ShortcutInfo implements Parcelable {
sb.append("disabledReason=");
sb.append(getDisabledReasonDebugString(mDisabledReason));
+ if (mStartingThemeResId != 0) {
+ addIndentOrComma(sb, indent);
+ sb.append("SplashScreenThemeResId=");
+ sb.append(Integer.toHexString(mStartingThemeResId));
+ }
+
addIndentOrComma(sb, indent);
sb.append("categories=");
@@ -2430,7 +2464,7 @@ public final class ShortcutInfo implements Parcelable {
Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras,
long lastChangedTimestamp,
int flags, int iconResId, String iconResName, String bitmapPath, String iconUri,
- int disabledReason, Person[] persons, LocusId locusId) {
+ int disabledReason, Person[] persons, LocusId locusId, int startingThemeResId) {
mUserId = userId;
mId = id;
mPackageName = packageName;
@@ -2459,5 +2493,6 @@ public final class ShortcutInfo implements Parcelable {
mDisabledReason = disabledReason;
mPersons = persons;
mLocusId = locusId;
+ mStartingThemeResId = startingThemeResId;
}
}
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index c62767ee031b..233abf36131b 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -71,6 +71,13 @@ public abstract class ShortcutServiceInternal {
public abstract int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId);
+ /**
+ * Get the theme res ID of the starting window, it can be 0 if not specified.
+ */
+ public abstract int getShortcutStartingThemeResId(int launcherUserId,
+ @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId,
+ int userId);
+
public abstract ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
@NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId);
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..35ccfca101d3
--- /dev/null
+++ b/core/java/android/window/SplashScreenView.java
@@ -0,0 +1,510 @@
+/*
+ * 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.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+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 mParceledIconBitmap;
+ private View mBrandingImageView;
+ private Bitmap mParceledBrandingBitmap;
+ private long mIconAnimationDuration;
+ private long mIconAnimationStart;
+
+ private Animatable mAnimatableIcon;
+ private ValueAnimator mAnimator;
+
+ // 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 mParceledIconBitmap;
+ private Drawable mIconDrawable;
+ private int mBrandingImageWidth;
+ private int mBrandingImageHeight;
+ private Drawable mBrandingDrawable;
+ private Bitmap mParceledBrandingBitmap;
+ private long mIconAnimationStart;
+ private long mIconAnimationDuration;
+
+ 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.mIconBitmap != null) {
+ mIconDrawable = new BitmapDrawable(mContext.getResources(), parcelable.mIconBitmap);
+ mParceledIconBitmap = parcelable.mIconBitmap;
+ }
+ if (parcelable.mBrandingBitmap != null) {
+ setBrandingDrawable(new BitmapDrawable(mContext.getResources(),
+ parcelable.mBrandingBitmap), parcelable.mBrandingWidth,
+ parcelable.mBrandingHeight);
+ mParceledBrandingBitmap = parcelable.mBrandingBitmap;
+ }
+ mIconAnimationStart = parcelable.mIconAnimationStart;
+ mIconAnimationDuration = parcelable.mIconAnimationDuration;
+ 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;
+ }
+
+ /**
+ * Set the animation duration if icon is animatable.
+ */
+ public Builder setAnimationDuration(int duration) {
+ mIconAnimationDuration = duration;
+ return this;
+ }
+
+ /**
+ * Set the Drawable object and size for the branding view.
+ */
+ public Builder setBrandingDrawable(Drawable branding, int width, int height) {
+ mBrandingDrawable = branding;
+ mBrandingImageWidth = width;
+ mBrandingImageHeight = height;
+ 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);
+ view.mBrandingImageView = view.findViewById(R.id.splashscreen_branding_view);
+ // center icon
+ 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);
+ view.initIconAnimation(mIconDrawable, mIconAnimationDuration);
+ }
+ view.mIconAnimationStart = mIconAnimationStart;
+ view.mIconAnimationDuration = mIconAnimationDuration;
+ if (mParceledIconBitmap != null) {
+ view.mParceledIconBitmap = mParceledIconBitmap;
+ }
+ // branding image
+ if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0) {
+ final ViewGroup.LayoutParams params = view.mBrandingImageView.getLayoutParams();
+ params.width = mBrandingImageWidth;
+ params.height = mBrandingImageHeight;
+ view.mBrandingImageView.setLayoutParams(params);
+ }
+ if (mBrandingDrawable != null) {
+ view.mBrandingImageView.setBackground(mBrandingDrawable);
+ }
+ if (mParceledBrandingBitmap != null) {
+ view.mParceledBrandingBitmap = mParceledBrandingBitmap;
+ }
+ if (DEBUG) {
+ Log.d(TAG, " build " + view + " Icon: view: " + view.mIconView + " drawable: "
+ + mIconDrawable + " size: " + mIconSize + "\n Branding: view: "
+ + view.mBrandingImageView + " drawable: " + mBrandingDrawable
+ + " size w: " + mBrandingImageWidth + " h: " + mBrandingImageHeight);
+ }
+ 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;
+ }
+
+ /**
+ * Returns the duration of the icon animation if icon is animatable.
+ *
+ * @see android.R.attr#windowSplashScreenAnimatedIcon
+ * @see android.R.attr#windowSplashScreenAnimationDuration
+ */
+ public long getIconAnimationDurationMillis() {
+ return mIconAnimationDuration;
+ }
+
+ /**
+ * If the replaced icon is animatable, return the animation start time in millisecond based on
+ * system. The start time is set using {@link SystemClock#uptimeMillis()}.
+ */
+ public long getIconAnimationStartMillis() {
+ return mIconAnimationStart;
+ }
+
+ void initIconAnimation(Drawable iconDrawable, long duration) {
+ if (iconDrawable instanceof Animatable) {
+ mAnimatableIcon = (Animatable) iconDrawable;
+ mAnimator = ValueAnimator.ofInt(0, 1);
+ mAnimator.setDuration(duration);
+ mAnimator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mIconAnimationStart = SystemClock.uptimeMillis();
+ mAnimatableIcon.start();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimatableIcon.stop();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mAnimatableIcon.stop();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ // do not repeat
+ mAnimatableIcon.stop();
+ }
+ });
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void startIntroAnimation() {
+ if (mAnimatableIcon != null) {
+ mAnimator.start();
+ }
+ }
+
+ /**
+ * <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 (mParceledIconBitmap != null) {
+ mIconView.setBackground(null);
+ mParceledIconBitmap.recycle();
+ mParceledIconBitmap = null;
+ }
+ if (mParceledBrandingBitmap != null) {
+ mBrandingImageView.setBackground(null);
+ mParceledBrandingBitmap.recycle();
+ mParceledBrandingBitmap = 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.
+ * @see android.R.attr#windowSplashScreenAnimatedIcon
+ */
+ public @Nullable View getIconView() {
+ return mIconView;
+ }
+
+ /**
+ * Get the branding image view.
+ * @hide
+ */
+ @TestApi
+ public @Nullable View getBrandingView() {
+ return mBrandingImageView;
+ }
+
+ /**
+ * 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 mIconBitmap;
+ private int mBrandingWidth;
+ private int mBrandingHeight;
+ private Bitmap mBrandingBitmap;
+
+ private long mIconAnimationStart;
+ private long mIconAnimationDuration;
+
+ public SplashScreenViewParcelable(SplashScreenView view) {
+ ViewGroup.LayoutParams params = view.getIconView().getLayoutParams();
+ mIconSize = params.height;
+ mBackgroundColor = view.getInitBackgroundColor();
+
+ mIconBitmap = copyDrawable(view.getIconView().getBackground());
+ mBrandingBitmap = copyDrawable(view.getBrandingView().getBackground());
+ params = view.getBrandingView().getLayoutParams();
+ mBrandingWidth = params.width;
+ mBrandingHeight = params.height;
+
+ mIconAnimationStart = view.getIconAnimationStartMillis();
+ mIconAnimationDuration = view.getIconAnimationDurationMillis();
+ }
+
+ private Bitmap copyDrawable(Drawable drawable) {
+ if (drawable != null) {
+ final Rect initialBounds = drawable.copyBounds();
+ final int width = initialBounds.width();
+ final int height = initialBounds.height();
+
+ final Bitmap snapshot = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas bmpCanvas = new Canvas(snapshot);
+ drawable.setBounds(0, 0, width, height);
+ drawable.draw(bmpCanvas);
+ final Bitmap copyBitmap = snapshot.createAshmemBitmap();
+ snapshot.recycle();
+ return copyBitmap;
+ }
+ return null;
+ }
+
+ private SplashScreenViewParcelable(@NonNull Parcel source) {
+ readParcel(source);
+ }
+
+ private void readParcel(@NonNull Parcel source) {
+ mIconSize = source.readInt();
+ mBackgroundColor = source.readInt();
+ mIconBitmap = source.readTypedObject(Bitmap.CREATOR);
+ mBrandingWidth = source.readInt();
+ mBrandingHeight = source.readInt();
+ mBrandingBitmap = source.readTypedObject(Bitmap.CREATOR);
+ mIconAnimationStart = source.readLong();
+ mIconAnimationDuration = source.readLong();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mIconSize);
+ dest.writeInt(mBackgroundColor);
+ dest.writeTypedObject(mIconBitmap, flags);
+ dest.writeInt(mBrandingWidth);
+ dest.writeInt(mBrandingHeight);
+ dest.writeTypedObject(mBrandingBitmap, flags);
+ dest.writeLong(mIconAnimationStart);
+ dest.writeLong(mIconAnimationDuration);
+ }
+
+ 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 (mIconBitmap != null) {
+ mIconBitmap.recycle();
+ mIconBitmap = null;
+ }
+ if (mBrandingBitmap != null) {
+ mBrandingBitmap.recycle();
+ mBrandingBitmap = null;
+ }
+ }
+
+ int getIconSize() {
+ return mIconSize;
+ }
+
+ int getBackgroundColor() {
+ return mBackgroundColor;
+ }
+ }
+}
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index 2282cc567936..63b9e9befb77 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -95,6 +95,12 @@ public final class StartingWindowInfo implements Parcelable {
*/
public int startingWindowTypeParameter;
+ /**
+ * Specifies a theme for the splash screen.
+ * @hide
+ */
+ public int splashScreenThemeResId;
+
public StartingWindowInfo() {
}
@@ -115,6 +121,7 @@ public final class StartingWindowInfo implements Parcelable {
dest.writeTypedObject(topOpaqueWindowInsetsState, flags);
dest.writeTypedObject(topOpaqueWindowLayoutParams, flags);
dest.writeTypedObject(mainWindowLayoutParams, flags);
+ dest.writeInt(splashScreenThemeResId);
}
void readFromParcel(@NonNull Parcel source) {
@@ -124,6 +131,7 @@ public final class StartingWindowInfo implements Parcelable {
topOpaqueWindowLayoutParams = source.readTypedObject(
WindowManager.LayoutParams.CREATOR);
mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR);
+ splashScreenThemeResId = source.readInt();
}
@Override
@@ -135,7 +143,8 @@ public final class StartingWindowInfo implements Parcelable {
+ Integer.toHexString(startingWindowTypeParameter)
+ " insetsState=" + topOpaqueWindowInsetsState
+ " topWindowLayoutParams=" + topOpaqueWindowLayoutParams
- + " mainWindowLayoutParams=" + mainWindowLayoutParams;
+ + " mainWindowLayoutParams=" + mainWindowLayoutParams
+ + " splashScreenThemeResId " + Integer.toHexString(splashScreenThemeResId);
}
public static final @android.annotation.NonNull Creator<StartingWindowInfo> CREATOR =
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..513da5e431e5
--- /dev/null
+++ b/core/res/res/layout/splash_screen_view.xml
@@ -0,0 +1,34 @@
+<?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"/>
+
+ <View android:id="@+id/splashscreen_branding_view"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_marginBottom="60dp"/>
+
+</android.window.SplashScreenView> \ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 823e10edaad5..927a8b49c17e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2262,6 +2262,23 @@
-->
<enum name="always" value="3" />
</attr>
+
+ <!-- The background color for the splash screen, if not specify then system will
+ calculate from windowBackground. -->
+ <attr name="windowSplashScreenBackground" format="color"/>
+
+ <!-- Replace an icon in the center of the starting window, if the object is animated
+ and drawable(e.g. AnimationDrawable, AnimatedVectorDrawable), then it will also
+ play the animation while showing the starting window. -->
+ <attr name="windowSplashScreenAnimatedIcon" format="reference"/>
+ <!-- The duration, in milliseconds, of the window splash screen icon animation duration
+ when playing the splash screen starting window. The maximum animation duration should
+ be limited below 1000ms. -->
+ <attr name="windowSplashScreenAnimationDuration" format="integer"/>
+
+ <!-- Place an drawable image in the bottom of the starting window, it can be used to
+ represent the branding of the application. -->
+ <attr name="windowSplashScreenBrandingImage" format="reference"/>
</declare-styleable>
<!-- The set of attributes that describe a AlertDialog's theme. -->
@@ -9260,6 +9277,7 @@
<attr name="shortcutShortLabel" format="reference" />
<attr name="shortcutLongLabel" format="reference" />
<attr name="shortcutDisabledMessage" format="reference" />
+ <attr name="splashScreenTheme" format="reference"/>
</declare-styleable>
<declare-styleable name="ShortcutCategories">
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 3c97aec5d55d..278708a26d44 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3066,6 +3066,11 @@
<public name="edgeEffectType" />
<public name="knownCerts" />
<public name="windowBackgroundBlurRadius"/>
+ <public name="windowSplashScreenBackground"/>
+ <public name="windowSplashScreenAnimatedIcon"/>
+ <public name="windowSplashScreenAnimationDuration"/>
+ <public name="windowSplashScreenBrandingImage"/>
+ <public name="splashScreenTheme" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4109d4c9f6f9..f66d33b02b69 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1818,6 +1818,9 @@
<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" />
+ <java-symbol type="id" name="splashscreen_branding_view" />
<!-- From services -->
<java-symbol type="anim" name="screen_rotate_0_enter" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 049ba23a1a97..87ae1623ca92 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -167,6 +167,9 @@ please see themes_device_defaults.xml.
<!-- Window attributes -->
<item name="windowBackground">@drawable/screen_background_selector_dark</item>
<item name="windowBackgroundFallback">?attr/colorBackground</item>
+ <item name="windowSplashScreenBackground">@color/transparent</item>
+ <item name="windowSplashScreenAnimatedIcon">@null</item>
+ <item name="windowSplashScreenBrandingImage">@null</item>
<item name="windowClipToOutline">false</item>
<item name="windowFrame">@null</item>
<item name="windowNoTitle">false</item>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 69c30f3ca05c..ded4a276880f 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -889,12 +889,6 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowSurfaceController.java"
},
- "-1088782910": {
- "message": "Translucent=%s Floating=%s ShowWallpaper=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STARTING_WINDOW",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"-1076978367": {
"message": "thawRotation: mRotation=%d",
"level": "VERBOSE",
@@ -1675,6 +1669,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/Task.java"
},
+ "-124316973": {
+ "message": "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STARTING_WINDOW",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-118786523": {
"message": "Resume failed; resetting state to %s: %s",
"level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 13f1fddfdfb6..24198659e15d 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -48,4 +48,7 @@
<!-- one handed background panel default alpha -->
<item name="config_one_handed_background_alpha" format="float" type="dimen">0.5</item>
+
+ <!-- maximum animation duration for the icon when entering the starting window -->
+ <integer name="max_starting_window_intro_icon_anim_duration">1000</integer>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 034e65c608a3..cb2aada2a9cf 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -173,4 +173,10 @@
<!-- The width/height of the icon view on staring surface. -->
<dimen name="starting_surface_icon_size">108dp</dimen>
+
+ <!-- The width of the brand image on staring surface. -->
+ <dimen name="starting_surface_brand_image_width">200dp</dimen>
+
+ <!-- The height of the brand image on staring surface. -->
+ <dimen name="starting_surface_brand_image_height">80dp</dimen>
</resources>
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..03724171da21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -25,6 +25,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Binder;
@@ -38,9 +40,6 @@ import android.window.StartingWindowInfo;
import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
@@ -111,23 +110,23 @@ public class ShellTaskOrganizer extends TaskOrganizer {
private final SizeCompatUIController mSizeCompatUI;
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
- this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */);
+ this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */,
+ new StartingSurfaceDrawer(context, mainExecutor));
}
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
SizeCompatUIController sizeCompatUI) {
- this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI);
+ this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI,
+ new StartingSurfaceDrawer(context, mainExecutor));
}
@VisibleForTesting
ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
- Context context, @Nullable SizeCompatUIController sizeCompatUI) {
+ Context context, @Nullable SizeCompatUIController sizeCompatUI,
+ StartingSurfaceDrawer startingSurfaceDrawer) {
super(taskOrganizerController, mainExecutor);
- // TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
- // by a controller, that class should be create while porting
- // ActivityRecord#addStartingWindow to WMShell.
- mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, mainExecutor);
mSizeCompatUI = sizeCompatUI;
+ mStartingSurfaceDrawer = startingSurfaceDrawer;
}
@Override
@@ -254,6 +253,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..45d551528940 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,23 +31,21 @@ 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;
/**
* Util class to create the view for a splash screen content.
+ * @hide
*/
-class SplashscreenContentDrawer {
+public class SplashscreenContentDrawer {
private static final String TAG = StartingSurfaceDrawer.TAG;
private static final boolean DEBUG = StartingSurfaceDrawer.DEBUG_SPLASH_SCREEN;
@@ -58,15 +56,24 @@ class SplashscreenContentDrawer {
// also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon.
private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f);
private final Context mContext;
+ private final int mMaxIconAnimationDuration;
+
private int mIconSize;
+ private int mBrandingImageWidth;
+ private int mBrandingImageHeight;
- SplashscreenContentDrawer(Context context) {
+ SplashscreenContentDrawer(Context context, int maxIconAnimationDuration) {
mContext = context;
+ mMaxIconAnimationDuration = maxIconAnimationDuration;
}
private void updateDensity() {
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.starting_surface_icon_size);
+ mBrandingImageWidth = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.starting_surface_brand_image_width);
+ mBrandingImageHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.starting_surface_brand_image_height);
}
private int getSystemBGColor() {
@@ -83,48 +90,92 @@ 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;
}
- final TypedArray typedArray = context.obtainStyledAttributes(
- com.android.internal.R.styleable.Window);
- final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
- typedArray.recycle();
+ final SplashScreenWindowAttrs attrs =
+ SplashScreenWindowAttrs.createWindowAttrs(context);
+ final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
final Drawable themeBGDrawable;
- if (resId == 0) {
+
+ if (attrs.mWindowBgColor != 0) {
+ themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
+ } else if (attrs.mWindowBgResId != 0) {
+ themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
+ } else {
Slog.w(TAG, "Window background not exist!");
themeBGDrawable = createDefaultBackgroundDrawable();
+ }
+ final int animationDuration;
+ final Drawable iconDrawable;
+ if (attrs.mReplaceIcon != null) {
+ iconDrawable = attrs.mReplaceIcon;
+ animationDuration = Math.max(0,
+ Math.min(attrs.mAnimationDuration, mMaxIconAnimationDuration));
} else {
- themeBGDrawable = context.getDrawable(resId);
+ iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
+ : context.getPackageManager().getDefaultActivityIcon();
+ animationDuration = 0;
}
- final Drawable iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
- : context.getPackageManager().getDefaultActivityIcon();
// 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();
+ .setIconDrawable(iconDrawable)
+ .setIconAnimationDuration(animationDuration)
+ .setBrandingDrawable(attrs.mBrandingImage).build();
+ }
+
+ private static class SplashScreenWindowAttrs {
+ private int mWindowBgResId = 0;
+ private int mWindowBgColor = Color.TRANSPARENT;
+ private Drawable mReplaceIcon = null;
+ private Drawable mBrandingImage = null;
+ private int mAnimationDuration = 0;
+
+ static SplashScreenWindowAttrs createWindowAttrs(Context context) {
+ final SplashScreenWindowAttrs attrs = new SplashScreenWindowAttrs();
+ final TypedArray typedArray = context.obtainStyledAttributes(
+ com.android.internal.R.styleable.Window);
+ attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
+ attrs.mWindowBgColor = typedArray.getColor(
+ R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
+ attrs.mReplaceIcon = typedArray.getDrawable(
+ R.styleable.Window_windowSplashScreenAnimatedIcon);
+ attrs.mAnimationDuration = typedArray.getInt(
+ R.styleable.Window_windowSplashScreenAnimationDuration, 0);
+ attrs.mBrandingImage = typedArray.getDrawable(
+ R.styleable.Window_windowSplashScreenBrandingImage);
+ typedArray.recycle();
+ if (DEBUG) {
+ Slog.d(TAG, "window attributes color: "
+ + Integer.toHexString(attrs.mWindowBgColor)
+ + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
+ + " brandImage " + attrs.mBrandingImage);
+ }
+ return attrs;
+ }
}
private class StartingWindowViewBuilder {
- // materials
private Drawable mThemeBGDrawable;
private Drawable mIconDrawable;
- private PhoneWindow mPhoneWindow;
+ private Window mWindow;
+ private int mIconAnimationDuration;
private Context mContext;
+ private Drawable mBrandingDrawable;
// result
private boolean mBuildComplete = false;
- private View mCachedResult;
+ private SplashScreenView mCachedResult;
private int mThemeColor;
private Drawable mFinalIconDrawable;
private float mScale = 1f;
@@ -141,8 +192,20 @@ class SplashscreenContentDrawer {
return this;
}
- StartingWindowViewBuilder setPhoneWindow(PhoneWindow window) {
- mPhoneWindow = window;
+ StartingWindowViewBuilder setWindow(Window window) {
+ mWindow = window;
+ mBuildComplete = false;
+ return this;
+ }
+
+ StartingWindowViewBuilder setIconAnimationDuration(int iconAnimationDuration) {
+ mIconAnimationDuration = iconAnimationDuration;
+ mBuildComplete = false;
+ return this;
+ }
+
+ StartingWindowViewBuilder setBrandingDrawable(Drawable branding) {
+ mBrandingDrawable = branding;
mBuildComplete = false;
return this;
}
@@ -153,11 +216,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 +236,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 +312,26 @@ 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);
}
+ builder.setAnimationDuration(mIconAnimationDuration);
+ if (mBrandingDrawable != null) {
+ builder.setBrandingDrawable(mBrandingDrawable, mBrandingImageWidth,
+ mBrandingImageHeight);
+ }
+ 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 +362,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 +380,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 +597,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..5332291fd1bd 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;
@@ -81,7 +82,10 @@ public class StartingSurfaceDrawer {
mContext = context;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mMainExecutor = mainExecutor;
- mSplashscreenContentDrawer = new SplashscreenContentDrawer(context);
+
+ final int maxIconAnimDuration = context.getResources().getInteger(
+ com.android.wm.shell.R.integer.max_starting_window_intro_icon_anim_duration);
+ mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, maxIconAnimDuration);
}
private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>();
@@ -193,9 +197,8 @@ public class StartingSurfaceDrawer {
public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
final PreferredStartingTypeHelper helper =
new PreferredStartingTypeHelper(windowInfo);
- final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
if (helper.mPreferredType == PreferredStartingTypeHelper.STARTING_TYPE_SPLASH_SCREEN) {
- addSplashScreenStartingWindow(runningTaskInfo, appToken);
+ addSplashScreenStartingWindow(windowInfo, appToken);
} else if (helper.mPreferredType == PreferredStartingTypeHelper.STARTING_TYPE_SNAPSHOT) {
final TaskSnapshot snapshot = helper.mSnapshot;
makeTaskSnapshotWindow(windowInfo, appToken, snapshot);
@@ -203,11 +206,13 @@ public class StartingSurfaceDrawer {
// If prefer don't show, then don't show!
}
- private void addSplashScreenStartingWindow(RunningTaskInfo taskInfo, IBinder appToken) {
+ private void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
+ final RunningTaskInfo taskInfo = windowInfo.taskInfo;
final ActivityInfo activityInfo = taskInfo.topActivityInfo;
if (activityInfo == null) {
return;
}
+
final int displayId = taskInfo.displayId;
if (activityInfo.packageName == null) {
return;
@@ -222,11 +227,11 @@ public class StartingSurfaceDrawer {
}
Context context = mContext;
- int theme = activityInfo.getThemeResource();
- if (theme == 0) {
- // replace with the default theme if the application didn't set
- theme = com.android.internal.R.style.Theme_DeviceDefault_DayNight;
- }
+ // replace with the default theme if the application didn't set
+ final int theme = windowInfo.splashScreenThemeResId != 0
+ ? windowInfo.splashScreenThemeResId
+ : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
+ : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
if (DEBUG_SPLASH_SCREEN) {
Slog.d(TAG, "addSplashScreen " + activityInfo.packageName
+ ": nonLocalizedLabel=" + nonLocalizedLabel + " theme="
@@ -352,9 +357,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 +372,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 +385,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 +399,60 @@ 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);
+ 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);
+ splashScreenView.startIntroAnimation();
+ mStartingWindowRecords.put(taskId, tView);
+ }
+ });
}
protected void removeWindowSynced(int taskId) {
@@ -459,10 +488,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/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 176b33dda020..c815e65189e6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -52,6 +52,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.sizecompatui.SizeCompatUIController;
+import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
import org.junit.Before;
import org.junit.Test;
@@ -77,6 +78,8 @@ public class ShellTaskOrganizerTests {
private Context mContext;
@Mock
private SizeCompatUIController mSizeCompatUI;
+ @Mock
+ private StartingSurfaceDrawer mStartingSurfaceDrawer;
ShellTaskOrganizer mOrganizer;
private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
@@ -112,7 +115,7 @@ public class ShellTaskOrganizerTests {
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
- mSizeCompatUI));
+ mSizeCompatUI, mStartingSurfaceDrawer));
}
@Test
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/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 7f638b9a55a2..915517a4b9ce 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -19,6 +19,7 @@ package com.android.server;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
+import static android.app.UiModeManager.MODE_NIGHT_NO;
import static android.app.UiModeManager.MODE_NIGHT_YES;
import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
@@ -82,6 +83,7 @@ import com.android.internal.util.DumpUtils;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
+import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
@@ -158,6 +160,7 @@ final class UiModeManagerService extends SystemService {
private NotificationManager mNotificationManager;
private StatusBarManager mStatusBarManager;
private WindowManagerInternal mWindowManager;
+ private ActivityTaskManagerInternal mActivityTaskManager;
private AlarmManager mAlarmManager;
private PowerManager mPowerManager;
@@ -366,6 +369,7 @@ final class UiModeManagerService extends SystemService {
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+ mActivityTaskManager = LocalServices.getService(ActivityTaskManagerInternal.class);
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
TwilightManager twilightManager = getLocalService(TwilightManager.class);
if (twilightManager != null) mTwilightManager = twilightManager;
@@ -750,6 +754,39 @@ final class UiModeManagerService extends SystemService {
}
@Override
+ public void setApplicationNightMode(@UiModeManager.NightMode int mode)
+ throws RemoteException {
+ switch (mode) {
+ case UiModeManager.MODE_NIGHT_NO:
+ case UiModeManager.MODE_NIGHT_YES:
+ case UiModeManager.MODE_NIGHT_AUTO:
+ case UiModeManager.MODE_NIGHT_CUSTOM:
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown mode: " + mode);
+ }
+ final int configNightMode;
+ switch (mode) {
+ case MODE_NIGHT_YES:
+ configNightMode = Configuration.UI_MODE_NIGHT_YES;
+ break;
+ case MODE_NIGHT_NO:
+ configNightMode = Configuration.UI_MODE_NIGHT_NO;
+ break;
+ default:
+ configNightMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
+ }
+ try {
+ final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
+ mActivityTaskManager.createPackageConfigurationUpdater();
+ updater.setNightMode(configNightMode);
+ updater.commit();
+ } catch (RemoteException e) {
+ throw e;
+ }
+ }
+
+ @Override
public boolean isUiModeLocked() {
synchronized (mLock) {
return mUiModeLocked;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index e91bb46657e1..b06e84d9df4e 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -935,6 +936,17 @@ public class LauncherAppsService extends SystemService {
intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intents[0].setSourceBounds(sourceBounds);
+ // Replace theme for splash screen
+ final int splashScreenThemeResId =
+ mShortcutServiceInternal.getShortcutStartingThemeResId(getCallingUserId(),
+ callingPackage, packageName, shortcutId, targetUserId);
+ if (splashScreenThemeResId != 0) {
+ if (startActivityOptions == null) {
+ startActivityOptions = new Bundle();
+ }
+ startActivityOptions.putInt(KEY_SPLASH_SCREEN_THEME, splashScreenThemeResId);
+ }
+
return startShortcutIntentsAsPublisher(
intents, packageName, featureId, startActivityOptions, targetUserId);
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 9b092c000172..bb4ec16be0a8 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -112,6 +112,7 @@ class ShortcutPackage extends ShortcutPackageItem {
private static final String ATTR_BITMAP_PATH = "bitmap-path";
private static final String ATTR_ICON_URI = "icon-uri";
private static final String ATTR_LOCUS_ID = "locus-id";
+ private static final String ATTR_SPLASH_SCREEN_THEME_ID = "splash-screen-theme-id";
private static final String ATTR_PERSON_NAME = "name";
private static final String ATTR_PERSON_URI = "uri";
@@ -1654,6 +1655,7 @@ class ShortcutPackage extends ShortcutPackageItem {
ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId());
ShortcutService.writeAttr(out, ATTR_TITLE_RES_NAME, si.getTitleResName());
+ ShortcutService.writeAttr(out, ATTR_SPLASH_SCREEN_THEME_ID, si.getStartingThemeResId());
ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId());
ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName());
@@ -1861,6 +1863,7 @@ class ShortcutPackage extends ShortcutPackageItem {
String bitmapPath;
String iconUri;
final String locusIdString;
+ int splashScreenThemeResId;
int backupVersionCode;
ArraySet<String> categories = null;
ArrayList<Person> persons = new ArrayList<>();
@@ -1871,6 +1874,8 @@ class ShortcutPackage extends ShortcutPackageItem {
title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
titleResId = ShortcutService.parseIntAttribute(parser, ATTR_TITLE_RES_ID);
titleResName = ShortcutService.parseStringAttribute(parser, ATTR_TITLE_RES_NAME);
+ splashScreenThemeResId = ShortcutService.parseIntAttribute(parser,
+ ATTR_SPLASH_SCREEN_THEME_ID);
text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT);
textResId = ShortcutService.parseIntAttribute(parser, ATTR_TEXT_RES_ID);
textResName = ShortcutService.parseStringAttribute(parser, ATTR_TEXT_RES_NAME);
@@ -1964,7 +1969,8 @@ class ShortcutPackage extends ShortcutPackageItem {
intents.toArray(new Intent[intents.size()]),
rank, extras, lastChangedTimestamp, flags,
iconResId, iconResName, bitmapPath, iconUri,
- disabledReason, persons.toArray(new Person[persons.size()]), locusId);
+ disabledReason, persons.toArray(new Person[persons.size()]), locusId,
+ splashScreenThemeResId);
}
private static Intent parseIntent(TypedXmlPullParser parser)
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
index d3aace19672a..c06f01a463ad 100644
--- a/services/core/java/com/android/server/pm/ShortcutParser.java
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -383,6 +383,8 @@ public class ShortcutParser {
final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutLongLabel, 0);
final int disabledMessageResId = sa.getResourceId(
R.styleable.Shortcut_shortcutDisabledMessage, 0);
+ final int splashScreenTheme = sa.getResourceId(
+ R.styleable.Shortcut_splashScreenTheme, 0);
if (TextUtils.isEmpty(id)) {
Log.w(TAG, "android:shortcutId must be provided. activity=" + activity);
@@ -404,7 +406,8 @@ public class ShortcutParser {
disabledMessageResId,
rank,
iconResId,
- enabled);
+ enabled,
+ splashScreenTheme);
} finally {
sa.recycle();
}
@@ -413,7 +416,7 @@ public class ShortcutParser {
private static ShortcutInfo createShortcutFromManifest(ShortcutService service,
@UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
int titleResId, int textResId, int disabledMessageResId,
- int rank, int iconResId, boolean enabled) {
+ int rank, int iconResId, boolean enabled, int splashScreenTheme) {
final int flags =
(enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
@@ -452,7 +455,8 @@ public class ShortcutParser {
null, // icon Url
disabledReason,
null /* persons */,
- null /* locusId */);
+ null /* locusId */,
+ splashScreenTheme);
}
private static String parseCategory(ShortcutService service, AttributeSet attrs) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 863e3fe5c6a3..4d8abea8acd4 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -3214,6 +3214,32 @@ public class ShortcutService extends IShortcutService.Stub {
}
@Override
+ public int getShortcutStartingThemeResId(int launcherUserId,
+ @NonNull String callingPackage, @NonNull String packageName,
+ @NonNull String shortcutId, int userId) {
+ Objects.requireNonNull(callingPackage, "callingPackage");
+ Objects.requireNonNull(packageName, "packageName");
+ Objects.requireNonNull(shortcutId, "shortcutId");
+
+ synchronized (mLock) {
+ throwIfUserLockedL(userId);
+ throwIfUserLockedL(launcherUserId);
+
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave();
+
+ final ShortcutPackage p = getUserShortcutsLocked(userId)
+ .getPackageShortcutsIfExists(packageName);
+ if (p == null) {
+ return 0;
+ }
+
+ final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
+ return shortcutInfo != null ? shortcutInfo.getStartingThemeResId() : 0;
+ }
+ }
+
+ @Override
public ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
@NonNull String callingPackage, @NonNull String packageName,
@NonNull String shortcutId, int userId) {
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..f440e566d47e 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.
@@ -1727,6 +1755,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (_createTime > 0) {
createTime = _createTime;
}
+ mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);
}
/**
@@ -1807,7 +1836,85 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return hasProcess() && app.hasThread();
}
- boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
+ /**
+ * Evaluate the theme for a starting window.
+ * @param originalTheme The original theme which read from activity or application.
+ * @param replaceTheme The replace theme which requested from starter.
+ * @return Resolved theme.
+ */
+ private int evaluateStartingWindowTheme(String pkg, int originalTheme, int replaceTheme) {
+ // Skip if the package doesn't want a starting window.
+ if (!validateStartingWindowTheme(pkg, originalTheme)) {
+ return 0;
+ }
+ int selectedTheme = originalTheme;
+ if (replaceTheme != 0 && validateStartingWindowTheme(pkg, replaceTheme)) {
+ // allow to replace theme
+ selectedTheme = replaceTheme;
+ }
+ return selectedTheme;
+ }
+
+ private boolean validateStartingWindowTheme(String pkg, int theme) {
+ // If this is a translucent window, then don't show a starting window -- the current
+ // effect (a full-screen opaque starting window that fades away to the real contents
+ // when it is ready) does not work for this.
+ ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Checking theme of starting window: 0x%x", theme);
+ if (theme != 0) {
+ AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
+ com.android.internal.R.styleable.Window,
+ mWmService.mCurrentUserId);
+ if (ent == null) {
+ // Whoops! App doesn't exist. Um. Okay. We'll just pretend like we didn't
+ // see that.
+ return false;
+ }
+ final boolean windowIsTranslucent = ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowIsTranslucent, false);
+ final boolean windowIsFloating = ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowIsFloating, false);
+ final boolean windowShowWallpaper = ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowShowWallpaper, false);
+ final boolean windowDisableStarting = ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowDisablePreview, false);
+ ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
+ "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s",
+ windowIsTranslucent, windowIsFloating, windowShowWallpaper,
+ windowDisableStarting);
+ if (windowIsTranslucent || windowIsFloating || windowDisableStarting) {
+ return false;
+ }
+ if (windowShowWallpaper
+ && getDisplayContent().mWallpaperController.getWallpaperTarget() != null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void applyStartingWindowTheme(String pkg, int theme) {
+ if (theme != 0) {
+ AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
+ com.android.internal.R.styleable.Window,
+ mWmService.mCurrentUserId);
+ if (ent == null) {
+ return;
+ }
+ final boolean windowShowWallpaper = ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowShowWallpaper, false);
+ if (windowShowWallpaper && getDisplayContent().mWallpaperController
+ .getWallpaperTarget() == null) {
+ // If this theme is requesting a wallpaper, and the wallpaper
+ // is not currently visible, then this effectively serves as
+ // an opaque window and our starting window transition animation
+ // can still work. We just need to make sure the starting window
+ // is also showing the wallpaper.
+ windowFlags |= FLAG_SHOW_WALLPAPER;
+ }
+ }
+ }
+
+ boolean addStartingWindow(String pkg, int resolvedTheme, CompatibilityInfo compatInfo,
CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
boolean allowTaskSnapshot, boolean activityCreated) {
@@ -1850,49 +1957,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return createSnapshot(snapshot, typeParameter);
}
- // If this is a translucent window, then don't show a starting window -- the current
- // effect (a full-screen opaque starting window that fades away to the real contents
- // when it is ready) does not work for this.
- ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Checking theme of starting window: 0x%x", theme);
- if (theme != 0) {
- AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
- com.android.internal.R.styleable.Window,
- mWmService.mCurrentUserId);
- if (ent == null) {
- // Whoops! App doesn't exist. Um. Okay. We'll just pretend like we didn't
- // see that.
- return false;
- }
- final boolean windowIsTranslucent = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowIsTranslucent, false);
- final boolean windowIsFloating = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowIsFloating, false);
- final boolean windowShowWallpaper = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowShowWallpaper, false);
- final boolean windowDisableStarting = ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowDisablePreview, false);
- ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Translucent=%s Floating=%s ShowWallpaper=%s",
- windowIsTranslucent, windowIsFloating, windowShowWallpaper);
- if (windowIsTranslucent) {
- return false;
- }
- if (windowIsFloating || windowDisableStarting) {
- return false;
- }
- if (windowShowWallpaper) {
- if (getDisplayContent().mWallpaperController
- .getWallpaperTarget() == null) {
- // If this theme is requesting a wallpaper, and the wallpaper
- // is not currently visible, then this effectively serves as
- // an opaque window and our starting window transition animation
- // can still work. We just need to make sure the starting window
- // is also showing the wallpaper.
- windowFlags |= FLAG_SHOW_WALLPAPER;
- } else {
- return false;
- }
- }
+ // Original theme can be 0 if developer doesn't request any theme. So if resolved theme is 0
+ // but original theme is not 0, means this package doesn't want a starting window.
+ if (resolvedTheme == 0 && theme != 0) {
+ return false;
}
+ applyStartingWindowTheme(pkg, resolvedTheme);
if (transferStartingWindow(transferFrom)) {
return true;
@@ -1906,7 +1976,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SplashScreenStartingData");
mStartingData = new SplashScreenStartingData(mWmService, pkg,
- theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
+ resolvedTheme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
getMergedOverrideConfiguration(), typeParameter);
scheduleAddStartingWindow();
return true;
@@ -2031,7 +2101,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 +5273,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 +5281,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
@@ -5931,7 +6122,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
pendingVoiceInteractionStart = false;
}
- void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
+ void showStartingWindow(boolean taskSwitch) {
+ showStartingWindow(null /* prev */, false /* newTask */, taskSwitch,
+ 0 /* splashScreenTheme */);
+ }
+
+ void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
+ int splashScreenTheme) {
if (mTaskOverlay) {
// We don't show starting window for overlay activities.
return;
@@ -5944,7 +6141,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final CompatibilityInfo compatInfo =
mAtmService.compatibilityInfoForPackageLocked(info.applicationInfo);
- final boolean shown = addStartingWindow(packageName, theme,
+
+ final int resolvedTheme = evaluateStartingWindowTheme(packageName, theme,
+ splashScreenTheme);
+ final boolean shown = addStartingWindow(packageName, resolvedTheme,
compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
allowTaskSnapshot(),
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 79f8229c6162..37fda4ce217a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1997,8 +1997,7 @@ class ActivityStarter {
if (mMovedToFront) {
// We moved the task to front, use starting window to hide initial drawn delay.
- targetTaskTop.showStartingWindow(null /* prev */, false /* newTask */,
- true /* taskSwitch */);
+ targetTaskTop.showStartingWindow(true /* taskSwitch */);
} else if (mDoResume) {
// Make sure the root task and its belonging display are moved to topmost.
mTargetRootTask.moveToFront("intentActivityFound");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 7d2075cca84d..94379b1f230e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -574,4 +574,26 @@ public abstract class ActivityTaskManagerInternal {
* @return Whether the package is the base of any locked task
*/
public abstract boolean isBaseOfLockedTask(String packageName);
+
+ /**
+ * Create an interface to update configuration for an application.
+ */
+ public abstract PackageConfigurationUpdater createPackageConfigurationUpdater();
+
+ /**
+ * An interface to update configuration for an application, and will persist override
+ * configuration for this package.
+ */
+ public interface PackageConfigurationUpdater {
+ /**
+ * Sets the dark mode for the current application. This setting is persisted and will
+ * override the system configuration for this application.
+ */
+ PackageConfigurationUpdater setNightMode(int nightMode);
+
+ /**
+ * Commit changes.
+ */
+ void commit() throws RemoteException;
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f16a646d00a1..2e98c2cbc5c2 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;
@@ -451,6 +452,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
/** The controller for all operations related to locktask. */
private LockTaskController mLockTaskController;
private ActivityStartController mActivityStartController;
+ PackageConfigPersister mPackageConfigPersister;
boolean mSuppressResizeConfigChanges;
@@ -866,6 +868,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
setRecentTasks(new RecentTasks(this, mTaskSupervisor));
mVrController = new VrController(mGlobalLock);
mKeyguardController = mTaskSupervisor.getKeyguardController();
+ mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue);
}
public void onActivityManagerInternalAdded() {
@@ -2027,8 +2030,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
// We are reshowing a task, use a starting window to hide the initial draw delay
// so the transition can start earlier.
- topActivity.showStartingWindow(null /* prev */, false /* newTask */,
- true /* taskSwitch */);
+ topActivity.showStartingWindow(true /* taskSwitch */);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -3249,6 +3251,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
@@ -5433,6 +5459,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
synchronized (mGlobalLock) {
mAppWarnings.onPackageUninstalled(name);
mCompatModePackages.handlePackageUninstalledLocked(name);
+ mPackageConfigPersister.onPackageUninstall(name);
}
}
@@ -6103,6 +6130,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
public void removeUser(int userId) {
synchronized (mGlobalLock) {
mRootWindowContainer.removeUser(userId);
+ mPackageConfigPersister.removeUser(userId);
}
}
@@ -6200,6 +6228,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
public void loadRecentTasksForUser(int userId) {
synchronized (mGlobalLock) {
mRecentTasks.loadUserRecentsLocked(userId);
+ // TODO renaming the methods(?)
+ mPackageConfigPersister.loadUserPackages(userId);
}
}
@@ -6308,5 +6338,54 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return getLockTaskController().isBaseOfLockedTask(packageName);
}
}
+
+ @Override
+ public PackageConfigurationUpdater createPackageConfigurationUpdater() {
+ synchronized (mGlobalLock) {
+ return new PackageConfigurationUpdaterImpl(Binder.getCallingPid());
+ }
+ }
+ }
+
+ final class PackageConfigurationUpdaterImpl implements
+ ActivityTaskManagerInternal.PackageConfigurationUpdater {
+ private int mPid;
+ private int mNightMode;
+
+ PackageConfigurationUpdaterImpl(int pid) {
+ mPid = pid;
+ }
+
+ @Override
+ public ActivityTaskManagerInternal.PackageConfigurationUpdater setNightMode(int nightMode) {
+ mNightMode = nightMode;
+ return this;
+ }
+
+ @Override
+ public void commit() throws RemoteException {
+ if (mPid == 0) {
+ throw new RemoteException("Invalid process");
+ }
+ synchronized (mGlobalLock) {
+ final WindowProcessController wpc = mProcessMap.getProcess(mPid);
+ if (wpc == null) {
+ Slog.w(TAG, "Override application configuration: cannot find application");
+ return;
+ }
+ if (wpc.getNightMode() == mNightMode) {
+ return;
+ }
+ if (!wpc.setOverrideNightMode(mNightMode)) {
+ return;
+ }
+ wpc.updateNightModeForAllActivities(mNightMode);
+ mPackageConfigPersister.updateFromImpl(wpc.mName, wpc.mUserId, this);
+ }
+ }
+
+ int getNightMode() {
+ return mNightMode;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index d90e88576909..efcaaa42ec13 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -534,6 +534,28 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
return getActivityType() == ACTIVITY_TYPE_ASSISTANT;
}
+ /**
+ * Overrides the night mode applied to this ConfigurationContainer.
+ * @return true if the nightMode has been changed.
+ */
+ public boolean setOverrideNightMode(int nightMode) {
+ final int currentUiMode = mFullConfiguration.uiMode;
+ final int currentNightMode = getNightMode();
+ final int validNightMode = nightMode & Configuration.UI_MODE_NIGHT_MASK;
+ if (currentNightMode == validNightMode) {
+ return false;
+ }
+ mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
+ mRequestsTmpConfig.uiMode = validNightMode
+ | (currentUiMode & ~Configuration.UI_MODE_NIGHT_MASK);
+ onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
+ return true;
+ }
+
+ int getNightMode() {
+ return mFullConfiguration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ }
+
public boolean isActivityTypeDream() {
return getActivityType() == ACTIVITY_TYPE_DREAM;
}
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
new file mode 100644
index 000000000000..1552a96d699a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -0,0 +1,380 @@
+/*
+ * 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 com.android.server.wm;
+
+import static android.app.UiModeManager.MODE_NIGHT_AUTO;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
+
+import android.annotation.NonNull;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+
+/**
+ * Persist configuration for each package, only persist the change if some on attributes are
+ * different from the global configuration. This class only applies to packages with Activities.
+ */
+public class PackageConfigPersister {
+ private static final String TAG = PackageConfigPersister.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final String TAG_CONFIG = "config";
+ private static final String ATTR_PACKAGE_NAME = "package_name";
+ private static final String ATTR_NIGHT_MODE = "night_mode";
+
+ private static final String PACKAGE_DIRNAME = "package_configs";
+ private static final String SUFFIX_FILE_NAME = "_config.xml";
+
+ private final PersisterQueue mPersisterQueue;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final SparseArray<HashMap<String, PackageConfigRecord>> mPendingWrite =
+ new SparseArray<>();
+ @GuardedBy("mLock")
+ private final SparseArray<HashMap<String, PackageConfigRecord>> mModified =
+ new SparseArray<>();
+
+ private static File getUserConfigsDir(int userId) {
+ return new File(Environment.getDataSystemCeDirectory(userId), PACKAGE_DIRNAME);
+ }
+
+ PackageConfigPersister(PersisterQueue queue) {
+ mPersisterQueue = queue;
+ }
+
+ @GuardedBy("mLock")
+ void loadUserPackages(int userId) {
+ synchronized (mLock) {
+ final File userConfigsDir = getUserConfigsDir(userId);
+ final File[] configFiles = userConfigsDir.listFiles();
+ if (configFiles == null) {
+ Slog.v(TAG, "loadPackages: empty list files from " + userConfigsDir);
+ return;
+ }
+
+ for (int fileIndex = 0; fileIndex < configFiles.length; ++fileIndex) {
+ final File configFile = configFiles[fileIndex];
+ if (DEBUG) {
+ Slog.d(TAG, "loadPackages: userId=" + userId
+ + ", configFile=" + configFile.getName());
+ }
+ if (!configFile.getName().endsWith(SUFFIX_FILE_NAME)) {
+ continue;
+ }
+
+ try (InputStream is = new FileInputStream(configFile)) {
+ final TypedXmlPullParser in = Xml.resolvePullParser(is);
+ int event;
+ String packageName = null;
+ int nightMode = MODE_NIGHT_AUTO;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
+ && event != XmlPullParser.END_TAG) {
+ final String name = in.getName();
+ if (event == XmlPullParser.START_TAG) {
+ if (DEBUG) {
+ Slog.d(TAG, "loadPackages: START_TAG name=" + name);
+ }
+ if (TAG_CONFIG.equals(name)) {
+ for (int attIdx = in.getAttributeCount() - 1; attIdx >= 0;
+ --attIdx) {
+ final String attrName = in.getAttributeName(attIdx);
+ final String attrValue = in.getAttributeValue(attIdx);
+ switch (attrName) {
+ case ATTR_PACKAGE_NAME:
+ packageName = attrValue;
+ break;
+ case ATTR_NIGHT_MODE:
+ nightMode = Integer.parseInt(attrValue);
+ break;
+ }
+ }
+ }
+ }
+ XmlUtils.skipCurrentTag(in);
+ }
+ if (packageName != null) {
+ final PackageConfigRecord initRecord =
+ findRecordOrCreate(mModified, packageName, userId);
+ initRecord.mNightMode = nightMode;
+ if (DEBUG) {
+ Slog.d(TAG, "loadPackages: load one package " + initRecord);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (XmlPullParserException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ void updateConfigIfNeeded(@NonNull ConfigurationContainer container, int userId,
+ String packageName) {
+ synchronized (mLock) {
+ final PackageConfigRecord modifiedRecord = findRecord(mModified, packageName, userId);
+ if (DEBUG) {
+ Slog.d(TAG,
+ "updateConfigIfNeeded record " + container + " find? " + modifiedRecord);
+ }
+ if (modifiedRecord != null) {
+ container.setOverrideNightMode(modifiedRecord.mNightMode);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ void updateFromImpl(String packageName, int userId,
+ ActivityTaskManagerService.PackageConfigurationUpdaterImpl impl) {
+ synchronized (mLock) {
+ PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId);
+ record.mNightMode = impl.getNightMode();
+
+ if (record.isResetNightMode()) {
+ removePackage(record.mName, record.mUserId);
+ } else {
+ final PackageConfigRecord pendingRecord =
+ findRecord(mPendingWrite, record.mName, record.mUserId);
+ final PackageConfigRecord writeRecord;
+ if (pendingRecord == null) {
+ writeRecord = findRecordOrCreate(mPendingWrite, record.mName,
+ record.mUserId);
+ } else {
+ writeRecord = pendingRecord;
+ }
+ if (writeRecord.mNightMode == record.mNightMode) {
+ return;
+ }
+ writeRecord.mNightMode = record.mNightMode;
+ if (DEBUG) {
+ Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord);
+ }
+ mPersisterQueue.addItem(new WriteProcessItem(writeRecord), false /* flush */);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ void removeUser(int userId) {
+ synchronized (mLock) {
+ final HashMap<String, PackageConfigRecord> modifyRecords = mModified.get(userId);
+ final HashMap<String, PackageConfigRecord> writeRecords = mPendingWrite.get(userId);
+ if ((modifyRecords == null || modifyRecords.size() == 0)
+ && (writeRecords == null || writeRecords.size() == 0)) {
+ return;
+ }
+ final HashMap<String, PackageConfigRecord> tempList = new HashMap<>(modifyRecords);
+ tempList.forEach((name, record) -> {
+ removePackage(record.mName, record.mUserId);
+ });
+ }
+ }
+
+ @GuardedBy("mLock")
+ void onPackageUninstall(String packageName) {
+ synchronized (mLock) {
+ for (int i = mModified.size() - 1; i > 0; i--) {
+ final int userId = mModified.keyAt(i);
+ removePackage(packageName, userId);
+ }
+ }
+ }
+
+ private void removePackage(String packageName, int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "removePackage packageName :" + packageName + " userId " + userId);
+ }
+ final PackageConfigRecord record = findRecord(mPendingWrite, packageName, userId);
+ if (record != null) {
+ removeRecord(mPendingWrite, record);
+ mPersisterQueue.removeItems(item ->
+ item.mRecord.mName == record.mName
+ && item.mRecord.mUserId == record.mUserId,
+ WriteProcessItem.class);
+ }
+
+ final PackageConfigRecord modifyRecord = findRecord(mModified, packageName, userId);
+ if (modifyRecord != null) {
+ removeRecord(mModified, modifyRecord);
+ mPersisterQueue.addItem(new DeletePackageItem(userId, packageName),
+ false /* flush */);
+ }
+ }
+
+ // store a changed data so we don't need to get the process
+ static class PackageConfigRecord {
+ final String mName;
+ final int mUserId;
+ int mNightMode;
+
+ PackageConfigRecord(String name, int userId) {
+ mName = name;
+ mUserId = userId;
+ }
+
+ boolean isResetNightMode() {
+ return mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM;
+ }
+
+ @Override
+ public String toString() {
+ return "PackageConfigRecord package name: " + mName + " userId " + mUserId
+ + " nightMode " + mNightMode;
+ }
+ }
+
+ private PackageConfigRecord findRecordOrCreate(
+ SparseArray<HashMap<String, PackageConfigRecord>> list, String name, int userId) {
+ HashMap<String, PackageConfigRecord> records = list.get(userId);
+ if (records == null) {
+ records = new HashMap<>();
+ list.put(userId, records);
+ }
+ PackageConfigRecord record = records.get(name);
+ if (record != null) {
+ return record;
+ }
+ record = new PackageConfigRecord(name, userId);
+ records.put(name, record);
+ return record;
+ }
+
+ private PackageConfigRecord findRecord(SparseArray<HashMap<String, PackageConfigRecord>> list,
+ String name, int userId) {
+ HashMap<String, PackageConfigRecord> packages = list.get(userId);
+ if (packages == null) {
+ return null;
+ }
+ return packages.get(name);
+ }
+
+ private void removeRecord(SparseArray<HashMap<String, PackageConfigRecord>> list,
+ PackageConfigRecord record) {
+ final HashMap<String, PackageConfigRecord> processes = list.get(record.mUserId);
+ if (processes != null) {
+ processes.remove(record.mName);
+ }
+ }
+
+ private static class DeletePackageItem implements PersisterQueue.WriteQueueItem {
+ final int mUserId;
+ final String mPackageName;
+
+ DeletePackageItem(int userId, String packageName) {
+ mUserId = userId;
+ mPackageName = packageName;
+ }
+
+ @Override
+ public void process() {
+ File userConfigsDir = getUserConfigsDir(mUserId);
+ if (!userConfigsDir.isDirectory()) {
+ return;
+ }
+ final AtomicFile atomicFile = new AtomicFile(new File(userConfigsDir,
+ mPackageName + SUFFIX_FILE_NAME));
+ if (atomicFile.exists()) {
+ atomicFile.delete();
+ }
+ }
+ }
+
+ private class WriteProcessItem implements PersisterQueue.WriteQueueItem {
+ final PackageConfigRecord mRecord;
+
+ WriteProcessItem(PackageConfigRecord record) {
+ mRecord = record;
+ }
+
+ @Override
+ public void process() {
+ // Write out one user.
+ byte[] data = null;
+ synchronized (mLock) {
+ try {
+ data = saveToXml();
+ } catch (Exception e) {
+ }
+ removeRecord(mPendingWrite, mRecord);
+ }
+ if (data != null) {
+ // Write out xml file while not holding mService lock.
+ FileOutputStream file = null;
+ AtomicFile atomicFile = null;
+ try {
+ File userConfigsDir = getUserConfigsDir(mRecord.mUserId);
+ if (!userConfigsDir.isDirectory() && !userConfigsDir.mkdirs()) {
+ Slog.e(TAG, "Failure creating tasks directory for user " + mRecord.mUserId
+ + ": " + userConfigsDir);
+ return;
+ }
+ atomicFile = new AtomicFile(new File(userConfigsDir,
+ mRecord.mName + SUFFIX_FILE_NAME));
+ file = atomicFile.startWrite();
+ file.write(data);
+ atomicFile.finishWrite(file);
+ } catch (IOException e) {
+ if (file != null) {
+ atomicFile.failWrite(file);
+ }
+ Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + e);
+ }
+ }
+ }
+
+ private byte[] saveToXml() throws IOException {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ final TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(os);
+
+ xmlSerializer.startDocument(null, true);
+ if (DEBUG) {
+ Slog.d(TAG, "Writing package configuration=" + mRecord);
+ }
+ xmlSerializer.startTag(null, TAG_CONFIG);
+ xmlSerializer.attribute(null, ATTR_PACKAGE_NAME, mRecord.mName);
+ xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode);
+ xmlSerializer.endTag(null, TAG_CONFIG);
+ xmlSerializer.endDocument();
+ xmlSerializer.flush();
+
+ return os.toByteArray();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ceebe9550846..6fbeaa4e944e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2656,7 +2656,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
void addStartingWindowsForVisibleActivities() {
forAllActivities((r) -> {
if (r.mVisibleRequested) {
- r.showStartingWindow(null /* prev */, false /* newTask */, true /*taskSwitch*/);
+ r.showStartingWindow(true /*taskSwitch*/);
}
});
}
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 94e14dd0a6b8..ef4a40f4837c 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -60,7 +60,7 @@ public class StartingSurfaceController {
final Task task = activity.getTask();
if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(task,
- activity.token)) {
+ activity.token, theme)) {
return new ShellStartingSurface(task);
}
return null;
@@ -125,7 +125,8 @@ public class StartingSurfaceController {
return mService.mTaskSnapshotController
.createStartingSurface(activity, taskSnapshot);
}
- mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token);
+ mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token,
+ 0 /* launchTheme */);
return new ShellStartingSurface(task);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9c8a997ec098..8bd4dfd054b6 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()
@@ -4267,6 +4275,9 @@ class Task extends WindowContainer<WindowContainer> {
if (mainWindow != null) {
info.mainWindowLayoutParams = mainWindow.getAttrs();
}
+ // If the developer has persist a different configuration, we need to override it to the
+ // starting window because persisted configuration does not effect to Task.
+ info.taskInfo.configuration.setTo(topActivity.getConfiguration());
}
final ActivityRecord topFullscreenActivity = getTopFullscreenActivity();
if (topFullscreenActivity != null) {
@@ -6558,10 +6569,9 @@ class Task extends WindowContainer<WindowContainer> {
Slog.i(TAG, "Restarting because process died: " + next);
if (!next.hasBeenLaunched) {
next.hasBeenLaunched = true;
- } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
+ } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
&& lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
- next.showStartingWindow(null /* prev */, false /* newTask */,
- false /* taskSwitch */);
+ next.showStartingWindow(false /* taskSwitch */);
}
mTaskSupervisor.startSpecificActivity(next, true, false);
return true;
@@ -6584,8 +6594,7 @@ class Task extends WindowContainer<WindowContainer> {
next.hasBeenLaunched = true;
} else {
if (SHOW_APP_STARTING_PREVIEW) {
- next.showStartingWindow(null /* prev */, false /* newTask */,
- false /* taskSwich */);
+ next.showStartingWindow(false /* taskSwich */);
}
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
}
@@ -6746,7 +6755,10 @@ class Task extends WindowContainer<WindowContainer> {
prev = null;
}
}
- r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity));
+ final int splashScreenThemeResId = options != null
+ ? options.getSplashScreenThemeResId() : 0;
+ r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity),
+ splashScreenThemeResId);
}
} else {
// If this is the first activity, don't do any fancy animations,
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 9fac3f003f70..286d31ed7d5f 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -117,8 +117,11 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
return mTaskOrganizer.asBinder();
}
- void addStartingWindow(Task task, IBinder appToken) {
+ void addStartingWindow(Task task, IBinder appToken, int launchTheme) {
final StartingWindowInfo info = task.getStartingWindowInfo();
+ if (launchTheme != 0) {
+ info.splashScreenThemeResId = launchTheme;
+ }
mDeferTaskOrgCallbacksConsumer.accept(() -> {
try {
mTaskOrganizer.addStartingWindow(info, appToken);
@@ -138,6 +141,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) {
@@ -232,14 +245,18 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
mUid = uid;
}
- void addStartingWindow(Task t, IBinder appToken) {
- mOrganizer.addStartingWindow(t, appToken);
+ void addStartingWindow(Task t, IBinder appToken, int launchTheme) {
+ mOrganizer.addStartingWindow(t, appToken, launchTheme);
}
void removeStartingWindow(Task t) {
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.
@@ -465,14 +482,14 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode);
}
- boolean addStartingWindow(Task task, IBinder appToken) {
+ boolean addStartingWindow(Task task, IBinder appToken, int launchTheme) {
final Task rootTask = task.getRootTask();
if (rootTask == null || rootTask.mTaskOrganizer == null) {
return false;
}
final TaskOrganizerState state =
mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
- state.addStartingWindow(task, appToken);
+ state.addStartingWindow(task, appToken, launchTheme);
return true;
}
@@ -486,6 +503,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/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 264a3b4edfa6..c3620235d3df 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -256,6 +256,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
onConfigurationChanged(atm.getGlobalConfiguration());
+ mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mName);
}
public void setPid(int pid) {
@@ -802,6 +803,13 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return false;
}
+ void updateNightModeForAllActivities(int nightMode) {
+ for (int i = mActivities.size() - 1; i >= 0; --i) {
+ final ActivityRecord r = mActivities.get(i);
+ r.setOverrideNightMode(nightMode);
+ }
+ }
+
public void clearPackagePreferredForHomeActivities() {
synchronized (mAtm.mGlobalLock) {
for (int i = mActivities.size() - 1; i >= 0; --i) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index be489c3bcd12..5614aa2a165d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -394,7 +394,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
"disabledMessage", 0, "disabledMessageResName",
null, null, 0, null, 0, 0,
0, "iconResName", "bitmapPath", null, 0,
- null, null);
+ null, null, 0);
return si;
}
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