Merge "Add shell command to enable/disable trace." into sc-dev
diff --git a/core/api/current.txt b/core/api/current.txt
index 8757830..9ef3eb4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1295,6 +1295,7 @@
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
@@ -1614,6 +1615,7 @@
field public static final int windowAllowReturnTransitionOverlap = 16843835; // 0x101043b
field public static final int windowAnimationStyle = 16842926; // 0x10100ae
field public static final int windowBackground = 16842836; // 0x1010054
+ field public static final int windowBackgroundBlurRadius = 16844331; // 0x101062b
field public static final int windowBackgroundFallback = 16844035; // 0x1010503
field public static final int windowBlurBehindEnabled = 16844316; // 0x101061c
field public static final int windowBlurBehindRadius = 16844315; // 0x101061b
@@ -1654,6 +1656,10 @@
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
@@ -3864,6 +3870,7 @@
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();
@@ -6652,6 +6659,7 @@
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);
@@ -9819,6 +9827,7 @@
method public int getMimeTypeCount();
method public long getTimestamp();
method public boolean hasMimeType(String);
+ method public boolean isStyledText();
method public void setExtras(android.os.PersistableBundle);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.ClipDescription> CREATOR;
@@ -12777,6 +12786,7 @@
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 {
@@ -49365,6 +49375,7 @@
method public void setAllowEnterTransitionOverlap(boolean);
method public void setAllowReturnTransitionOverlap(boolean);
method public void setAttributes(android.view.WindowManager.LayoutParams);
+ method public void setBackgroundBlurRadius(int);
method public abstract void setBackgroundDrawable(android.graphics.drawable.Drawable);
method public void setBackgroundDrawableResource(@DrawableRes int);
method public void setCallback(android.view.Window.Callback);
@@ -55821,6 +55832,25 @@
}
+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/module-lib-current.txt b/core/api/module-lib-current.txt
index 4e25625..51d5139 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -223,6 +223,14 @@
field @NonNull public final java.util.List<java.lang.String> underlyingIfaces;
}
+ public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+ ctor public VpnTransportInfo(int);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR;
+ field public final int type;
+ }
+
}
package android.os {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1c36a24..b261d92 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -246,6 +246,7 @@
field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
field public static final String SHUTDOWN = "android.permission.SHUTDOWN";
+ field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS";
field public static final String SOUND_TRIGGER_RUN_IN_BATTERY_SAVER = "android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER";
field public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission.START_ACTIVITIES_FROM_BACKGROUND";
field public static final String START_FOREGROUND_SERVICES_FROM_BACKGROUND = "android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND";
@@ -10385,6 +10386,7 @@
method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onEndSession(@NonNull String) throws java.io.IOException;
method public void onFreeCache(@NonNull java.util.UUID, long) throws java.io.IOException;
+ method public long onGetAnrDelayMillis(@NonNull String, int);
method public abstract void onStartSession(@NonNull String, int, @NonNull android.os.ParcelFileDescriptor, @NonNull java.io.File, @NonNull java.io.File) throws java.io.IOException;
method public abstract void onVolumeStateChanged(@NonNull android.os.storage.StorageVolume) throws java.io.IOException;
field public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 2; // 0x2
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 694507d..29be5c6 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1348,6 +1348,12 @@
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
+ public class NetworkPolicyManager {
+ method public boolean getRestrictBackground();
+ method @NonNull public static String resolveNetworkId(@NonNull android.net.wifi.WifiConfiguration);
+ method public void setRestrictBackground(boolean);
+ }
+
public class NetworkStack {
method public static void setServiceForTest(@Nullable android.os.IBinder);
}
@@ -2723,6 +2729,10 @@
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();
@@ -2742,6 +2752,7 @@
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/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 3c67e44..87fb5b1 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -487,6 +487,8 @@
GetterSetterNames: android.location.LocationRequest#isLowPowerMode():
+GetterSetterNames: android.net.NetworkPolicyManager#getRestrictBackground():
+ Symmetric method for `setRestrictBackground` must be named `isRestrictBackground`; was `getRestrictBackground`
GetterSetterNames: android.os.IncidentReportArgs#isAll():
GetterSetterNames: android.service.notification.NotificationStats#setDirectReplied():
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4728f11..992d054 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -139,6 +139,8 @@
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 @@
private UiTranslationController mUiTranslationController;
+ private SplashScreen mSplashScreen;
+ /** @hide */
+ SplashScreenView mSplashScreenView;
+
private final WindowControllerCallback mWindowControllerCallback =
new WindowControllerCallback() {
/**
@@ -1603,6 +1609,23 @@
}
/**
+ * 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 401f8cc..e3b5e9a 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -46,9 +46,9 @@
}
/** 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 @@
}
}
+ /**
+ * 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 28da1c3..73cc13c8 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -160,6 +160,12 @@
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 @@
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 @@
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 @@
}
/**
+ * 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 @@
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 70fa444..233f737 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -19,6 +19,7 @@
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.util.DisplayMetrics;
import android.util.Singleton;
import android.view.RemoteAnimationDefinition;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
import java.util.List;
@@ -243,6 +245,22 @@
}
/**
+ * 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 bb6a774..3d9f612 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -171,12 +171,15 @@
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.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.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 @@
/** 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 @@
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 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 @@
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 @@
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 @@
}
/**
+ * 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 @@
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 0e1c827..cf5fd14 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -28,6 +28,7 @@
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 @@
/** 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 9d3286f..bb743b8 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -34,7 +34,7 @@
*/
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 @@
* 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 38a3e70..542f754 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -70,6 +70,7 @@
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 @@
* 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 0ba5bec..f71eebdc 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -62,6 +62,16 @@
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 e1c262c..9b99ab8 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -477,11 +477,13 @@
* 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 @@
}
/**
+ * 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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ff6f6a0..907abb2 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1300,10 +1300,11 @@
* A value for {@link #EXTRA_PROVISIONING_SUPPORTED_MODES} indicating that provisioning is
* organization-owned.
*
- * <p>Using this value will cause the admin app's {@link #ACTION_GET_PROVISIONING_MODE}
- * activity to have the {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra
- * contain {@link #PROVISIONING_MODE_MANAGED_PROFILE} and {@link
- * #PROVISIONING_MODE_FULLY_MANAGED_DEVICE}.
+ * <p>Using this value indicates the admin app can only be provisioned in either a
+ * fully-managed device or a corporate-owned work profile. This will cause the admin app's
+ * {@link #ACTION_GET_PROVISIONING_MODE} activity to have the {@link
+ * #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link
+ * #PROVISIONING_MODE_MANAGED_PROFILE} and {@link #PROVISIONING_MODE_FULLY_MANAGED_DEVICE}.
*
* <p>Also, if this value is set, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity
* will not receive the {@link #EXTRA_PROVISIONING_IMEI} and {@link
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 7e232ac..85cfe83 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -401,7 +401,8 @@
* @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
*/
public void onFullBackup(FullBackupDataOutput data) throws IOException {
- FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this);
+ FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this,
+ mOperationType);
if (!isDeviceToDeviceMigration() && !backupScheme.isFullBackupContentEnabled()) {
return;
}
@@ -624,7 +625,8 @@
if (includeMap == null || includeMap.size() == 0) {
// Do entire sub-tree for the provided token.
fullBackupFileTree(packageName, domainToken,
- FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken),
+ FullBackup.getBackupScheme(this, mOperationType)
+ .tokenToDirectoryPath(domainToken),
filterSet, traversalExcludeSet, data);
} else if (includeMap.get(domainToken) != null) {
// This will be null if the xml parsing didn't yield any rules for
@@ -795,7 +797,8 @@
ArraySet<String> systemExcludes,
FullBackupDataOutput output) {
// Pull out the domain and set it aside to use when making the tarball.
- String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
+ String domainPath = FullBackup.getBackupScheme(this, mOperationType)
+ .tokenToDirectoryPath(domain);
if (domainPath == null) {
// Should never happen.
return;
@@ -911,7 +914,7 @@
return true;
}
- FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
+ FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mOperationType);
if (!bs.isFullBackupContentEnabled()) {
if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(FullBackup.TAG_XML_PARSER,
@@ -985,7 +988,8 @@
+ " domain=" + domain + " relpath=" + path + " mode=" + mode
+ " mtime=" + mtime);
- basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
+ basePath = FullBackup.getBackupScheme(this, mOperationType).tokenToDirectoryPath(
+ domain);
if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
mode = -1; // < 0 is a token to skip attempting a chmod()
}
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 742d05c..f7ed6f1f 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -16,6 +16,9 @@
package android.app.backup;
+import static android.app.backup.BackupManager.OperationType;
+
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -41,6 +44,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -90,27 +94,61 @@
"fakeClientSideEncryption";
/**
+ * Identify {@link BackupScheme} object by package and operation type
+ * (see {@link OperationType}) it corresponds to.
+ */
+ private static class BackupSchemeId {
+ final String mPackageName;
+ @OperationType final int mOperationType;
+
+ BackupSchemeId(String packageName, @OperationType int operationType) {
+ mPackageName = packageName;
+ mOperationType = operationType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPackageName, mOperationType);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (object == null || getClass() != object.getClass()) {
+ return false;
+ }
+ BackupSchemeId that = (BackupSchemeId) object;
+ return Objects.equals(mPackageName, that.mPackageName) &&
+ Objects.equals(mOperationType, that.mOperationType);
+ }
+ }
+
+ /**
* @hide
*/
@UnsupportedAppUsage
static public native int backupToTar(String packageName, String domain,
String linkdomain, String rootpath, String path, FullBackupDataOutput output);
- private static final Map<String, BackupScheme> kPackageBackupSchemeMap =
- new ArrayMap<String, BackupScheme>();
+ private static final Map<BackupSchemeId, BackupScheme> kPackageBackupSchemeMap =
+ new ArrayMap<>();
- static synchronized BackupScheme getBackupScheme(Context context) {
+ static synchronized BackupScheme getBackupScheme(Context context,
+ @OperationType int operationType) {
+ BackupSchemeId backupSchemeId = new BackupSchemeId(context.getPackageName(), operationType);
BackupScheme backupSchemeForPackage =
- kPackageBackupSchemeMap.get(context.getPackageName());
+ kPackageBackupSchemeMap.get(backupSchemeId);
if (backupSchemeForPackage == null) {
- backupSchemeForPackage = new BackupScheme(context);
- kPackageBackupSchemeMap.put(context.getPackageName(), backupSchemeForPackage);
+ backupSchemeForPackage = new BackupScheme(context, operationType);
+ kPackageBackupSchemeMap.put(backupSchemeId, backupSchemeForPackage);
}
return backupSchemeForPackage;
}
public static BackupScheme getBackupSchemeForTest(Context context) {
- BackupScheme testing = new BackupScheme(context);
+ BackupScheme testing = new BackupScheme(context, OperationType.BACKUP);
testing.mExcludes = new ArraySet();
testing.mIncludes = new ArrayMap();
return testing;
@@ -236,6 +274,7 @@
private final static String TAG_EXCLUDE = "exclude";
final int mFullBackupContent;
+ @OperationType final int mOperationType;
final PackageManager mPackageManager;
final StorageManager mStorageManager;
final String mPackageName;
@@ -354,8 +393,9 @@
*/
ArraySet<PathWithRequiredFlags> mExcludes;
- BackupScheme(Context context) {
+ BackupScheme(Context context, @OperationType int operationType) {
mFullBackupContent = context.getApplicationInfo().fullBackupContent;
+ mOperationType = operationType;
mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
mPackageManager = context.getPackageManager();
mPackageName = context.getPackageName();
diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java
index d451599..e6fdc00 100644
--- a/core/java/android/app/servertransaction/ResumeActivityItem.java
+++ b/core/java/android/app/servertransaction/ResumeActivityItem.java
@@ -60,7 +60,7 @@
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 0000000..5374984
--- /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/ClipData.java b/core/java/android/content/ClipData.java
index 0188637..f3ecbf6 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -740,6 +740,7 @@
mIcon = null;
mItems = new ArrayList<Item>();
mItems.add(item);
+ mClipDescription.setIsStyledText(isStyledText());
}
/**
@@ -756,6 +757,7 @@
mIcon = null;
mItems = new ArrayList<Item>();
mItems.add(item);
+ mClipDescription.setIsStyledText(isStyledText());
}
/**
@@ -914,6 +916,9 @@
throw new NullPointerException("item is null");
}
mItems.add(item);
+ if (mItems.size() == 1) {
+ mClipDescription.setIsStyledText(isStyledText());
+ }
}
/**
@@ -1049,6 +1054,20 @@
}
}
+ private boolean isStyledText() {
+ if (mItems.isEmpty()) {
+ return false;
+ }
+ final CharSequence text = mItems.get(0).getText();
+ if (text instanceof Spanned) {
+ Spanned spanned = (Spanned) text;
+ if (TextUtils.hasStyleSpan(spanned)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public String toString() {
StringBuilder b = new StringBuilder(128);
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index e3395e2..d48f832 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -120,6 +120,7 @@
private final ArrayList<String> mMimeTypes;
private PersistableBundle mExtras;
private long mTimeStamp;
+ private boolean mIsStyledText;
/**
* Create a new clip.
@@ -325,6 +326,26 @@
}
}
+ /**
+ * Returns true if the first item of the associated {@link ClipData} contains styled text, i.e.
+ * if it contains spans such as {@link android.text.style.CharacterStyle CharacterStyle}, {@link
+ * android.text.style.ParagraphStyle ParagraphStyle}, or {@link
+ * android.text.style.UpdateAppearance UpdateAppearance}. Returns false if it does not, or if
+ * there is no associated clip data.
+ */
+ public boolean isStyledText() {
+ return mIsStyledText;
+ }
+
+ /**
+ * Sets whether the associated {@link ClipData} contains styled text in its first item. This
+ * should be called when this description is associated with clip data or when the first item
+ * is added to the associated clip data.
+ */
+ void setIsStyledText(boolean isStyledText) {
+ mIsStyledText = isStyledText;
+ }
+
@Override
public String toString() {
StringBuilder b = new StringBuilder(128);
@@ -429,6 +450,7 @@
dest.writeStringList(mMimeTypes);
dest.writePersistableBundle(mExtras);
dest.writeLong(mTimeStamp);
+ dest.writeBoolean(mIsStyledText);
}
ClipDescription(Parcel in) {
@@ -436,6 +458,7 @@
mMimeTypes = in.createStringArrayList();
mExtras = in.readPersistableBundle();
mTimeStamp = in.readLong();
+ mIsStyledText = in.readBoolean();
}
public static final @android.annotation.NonNull Parcelable.Creator<ClipDescription> CREATOR =
diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index 85549d8..ebe202b 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -273,7 +273,7 @@
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 522f4ca..ce0547f 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -434,6 +434,8 @@
private int mDisabledReason;
+ private int mStartingThemeResId;
+
private ShortcutInfo(Builder b) {
mUserId = b.mContext.getUserId();
@@ -462,6 +464,7 @@
mLocusId = b.mLocusId;
updateTimestamp();
+ mStartingThemeResId = b.mStartingThemeResId;
}
/**
@@ -608,6 +611,7 @@
// Set this bit.
mFlags |= FLAG_KEY_FIELDS_ONLY;
}
+ mStartingThemeResId = source.mStartingThemeResId;
}
/**
@@ -931,6 +935,9 @@
if (source.mLocusId != null) {
mLocusId = source.mLocusId;
}
+ if (source.mStartingThemeResId != 0) {
+ mStartingThemeResId = source.mStartingThemeResId;
+ }
}
/**
@@ -1000,6 +1007,8 @@
private LocusId mLocusId;
+ private int mStartingThemeResId;
+
/**
* Old style constructor.
* @hide
@@ -1102,6 +1111,15 @@
}
/**
+ * 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 @@
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 @@
mPersons = source.readParcelableArray(cl, Person.class);
mLocusId = source.readParcelable(cl);
mIconUri = source.readString8();
+ mStartingThemeResId = source.readInt();
}
@Override
@@ -2189,6 +2216,7 @@
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 @@
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 @@
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 @@
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 c62767e..233abf3 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -71,6 +71,13 @@
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/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 3e6237d..6353a25 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.app.ActivityManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -55,6 +56,7 @@
*
* @hide
*/
+@TestApi
@SystemService(Context.NETWORK_POLICY_SERVICE)
public class NetworkPolicyManager {
@@ -125,6 +127,7 @@
public static final int RULE_REJECT_ALL = 1 << 6;
/**
* Reject traffic on all networks for restricted networking mode.
+ * @hide
*/
public static final int RULE_REJECT_RESTRICTED_MODE = 1 << 10;
@@ -351,6 +354,7 @@
}
/** @hide */
+ @TestApi
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void setRestrictBackground(boolean restrictBackground) {
try {
@@ -361,6 +365,7 @@
}
/** @hide */
+ @TestApi
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean getRestrictBackground() {
try {
@@ -506,6 +511,8 @@
/**
* Get multipath preference for the given network.
+ *
+ * @hide
*/
public int getMultipathPreference(Network network) {
try {
@@ -624,7 +631,9 @@
}
/** @hide */
- public static String resolveNetworkId(WifiConfiguration config) {
+ @TestApi
+ @NonNull
+ public static String resolveNetworkId(@NonNull WifiConfiguration config) {
return WifiInfo.sanitizeSsid(config.isPasspoint()
? config.providerFriendlyName : config.SSID);
}
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index dac1ede..a04047d 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -1,4 +1,6 @@
# Haptics
+per-file CombinedVibrationEffect.aidl = michaelwr@google.com
+per-file CombinedVibrationEffect.java = michaelwr@google.com
per-file ExternalVibration.aidl = michaelwr@google.com
per-file ExternalVibration.java = michaelwr@google.com
per-file IExternalVibrationController.aidl = michaelwr@google.com
@@ -6,9 +8,11 @@
per-file IVibratorManagerService.aidl = michaelwr@google.com
per-file NullVibrator.java = michaelwr@google.com
per-file SystemVibrator.java = michaelwr@google.com
+per-file SystemVibratorManager.java = michaelwr@google.com
per-file VibrationEffect.aidl = michaelwr@google.com
per-file VibrationEffect.java = michaelwr@google.com
per-file Vibrator.java = michaelwr@google.com
+per-file VibratorManager.java = michaelwr@google.com
# PowerManager
per-file IPowerManager.aidl = michaelwr@google.com, santoscordon@google.com
diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java
index a750b68..87add57 100644
--- a/core/java/android/service/storage/ExternalStorageService.java
+++ b/core/java/android/service/storage/ExternalStorageService.java
@@ -95,6 +95,21 @@
public static final String EXTRA_ERROR =
"android.service.storage.extra.error";
+ /**
+ * {@link Bundle} key for a package name {@link String} value.
+ *
+ * {@hide}
+ */
+ public static final String EXTRA_PACKAGE_NAME = "android.service.storage.extra.package_name";
+
+ /**
+ * {@link Bundle} key for a {@link Long} value.
+ *
+ * {@hide}
+ */
+ public static final String EXTRA_ANR_TIMEOUT_MS =
+ "android.service.storage.extra.anr_timeout_ms";
+
/** @hide */
@IntDef(flag = true, prefix = {"FLAG_SESSION_"},
value = {FLAG_SESSION_TYPE_FUSE, FLAG_SESSION_ATTRIBUTE_INDEXABLE})
@@ -162,6 +177,15 @@
throw new UnsupportedOperationException("onFreeCacheRequested not implemented");
}
+ /**
+ * Called when {@code packageName} is about to ANR
+ *
+ * @return ANR dialog delay in milliseconds
+ */
+ public long onGetAnrDelayMillis(@NonNull String packageName, int uid) {
+ throw new UnsupportedOperationException("onGetAnrDelayMillis not implemented");
+ }
+
@Override
@NonNull
public final IBinder onBind(@NonNull Intent intent) {
@@ -222,6 +246,19 @@
});
}
+ @Override
+ public void getAnrDelayMillis(String packageName, int uid, RemoteCallback callback)
+ throws RemoteException {
+ mHandler.post(() -> {
+ try {
+ long timeoutMs = onGetAnrDelayMillis(packageName, uid);
+ sendTimeoutResult(packageName, timeoutMs, null /* throwable */, callback);
+ } catch (Throwable t) {
+ sendTimeoutResult(packageName, 0 /* timeoutMs */, t, callback);
+ }
+ });
+ }
+
private void sendResult(String sessionId, Throwable throwable, RemoteCallback callback) {
Bundle bundle = new Bundle();
bundle.putString(EXTRA_SESSION_ID, sessionId);
@@ -230,5 +267,16 @@
}
callback.sendResult(bundle);
}
+
+ private void sendTimeoutResult(String packageName, long timeoutMs, Throwable throwable,
+ RemoteCallback callback) {
+ Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_PACKAGE_NAME, packageName);
+ bundle.putLong(EXTRA_ANR_TIMEOUT_MS, timeoutMs);
+ if (throwable != null) {
+ bundle.putParcelable(EXTRA_ERROR, new ParcelableException(throwable));
+ }
+ callback.sendResult(bundle);
+ }
}
}
diff --git a/core/java/android/service/storage/IExternalStorageService.aidl b/core/java/android/service/storage/IExternalStorageService.aidl
index d06671b..2e0bd86 100644
--- a/core/java/android/service/storage/IExternalStorageService.aidl
+++ b/core/java/android/service/storage/IExternalStorageService.aidl
@@ -32,4 +32,5 @@
in RemoteCallback callback);
void freeCache(@utf8InCpp String sessionId, in String volumeUuid, long bytes,
in RemoteCallback callback);
+ void getAnrDelayMillis(String packageName, int uid, in RemoteCallback callback);
}
\ No newline at end of file
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6b13a29..228ee3c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2836,8 +2836,7 @@
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 @@
};
}
+ /**
+ * @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 @@
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/view/Window.java b/core/java/android/view/Window.java
index af18293..4ecdd78 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1703,6 +1703,30 @@
public abstract void setBackgroundDrawable(Drawable drawable);
/**
+ * Blurs the screen behind the window within the bounds of the window.
+ *
+ * The density of the blur is set by the blur radius. The radius defines the size
+ * of the neighbouring area, from which pixels will be averaged to form the final
+ * color for each pixel. The operation approximates a Gaussian blur.
+ * A radius of 0 means no blur. The higher the radius, the denser the blur.
+ *
+ * The window background drawable is drawn on top of the blurred region. The blur
+ * region bounds and rounded corners will mimic those of the background drawable.
+ *
+ * For the blur region to be visible, the window has to be translucent. See
+ * {@link android.R.styleable#Window_windowIsTranslucent}.
+ *
+ * Note the difference with {@link android.view.WindowManager.LayoutParams#blurBehindRadius},
+ * which blurs the whole screen behind the window. Background blur blurs the screen behind
+ * only within the bounds of the window.
+ *
+ * @param blurRadius The blur radius to use for window background blur in pixels
+ *
+ * @see android.R.styleable#Window_windowBackgroundBlurRadius
+ */
+ public void setBackgroundBlurRadius(int blurRadius) {}
+
+ /**
* Set the value for a drawable feature of this window, from a resource
* identifier. You must have called requestFeature(featureId) before
* calling this function.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fe37c53..0f2089a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12940,17 +12940,11 @@
return false;
}
- final ClipData clipData = getClipboardManagerForUser().getPrimaryClip();
- final ClipDescription description = clipData.getDescription();
+ final ClipDescription description =
+ getClipboardManagerForUser().getPrimaryClipDescription();
final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
- final CharSequence text = clipData.getItemAt(0).getText();
- if (isPlainType && (text instanceof Spanned)) {
- Spanned spanned = (Spanned) text;
- if (TextUtils.hasStyleSpan(spanned)) {
- return true;
- }
- }
- return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
+ return (isPlainType && description.isStyledText())
+ || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
}
boolean canProcessText() {
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index 88b2257..8f541d0 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -42,6 +42,11 @@
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 0000000..4b88a9b
--- /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 0000000..cc7ac1e
--- /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 0000000..35ccfca
--- /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 2282cc5..63b9e9b 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -95,6 +95,12 @@
*/
public int startingWindowTypeParameter;
+ /**
+ * Specifies a theme for the splash screen.
+ * @hide
+ */
+ public int splashScreenThemeResId;
+
public StartingWindowInfo() {
}
@@ -115,6 +121,7 @@
dest.writeTypedObject(topOpaqueWindowInsetsState, flags);
dest.writeTypedObject(topOpaqueWindowLayoutParams, flags);
dest.writeTypedObject(mainWindowLayoutParams, flags);
+ dest.writeInt(splashScreenThemeResId);
}
void readFromParcel(@NonNull Parcel source) {
@@ -124,6 +131,7 @@
topOpaqueWindowLayoutParams = source.readTypedObject(
WindowManager.LayoutParams.CREATOR);
mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR);
+ splashScreenThemeResId = source.readInt();
}
@Override
@@ -135,7 +143,8 @@
+ 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 cdb4762..217ade8 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -105,6 +105,12 @@
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 @@
}
@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/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index adebde0..9840013 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -110,6 +110,7 @@
import android.widget.PopupWindow;
import com.android.internal.R;
+import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
import com.android.internal.policy.PhoneWindow.PanelFeatureState;
import com.android.internal.policy.PhoneWindow.PhoneWindowMenuCallback;
import com.android.internal.view.FloatingActionMode;
@@ -255,6 +256,7 @@
private Drawable mOriginalBackgroundDrawable;
private Drawable mLastOriginalBackgroundDrawable;
private Drawable mResizingBackgroundDrawable;
+ private BackgroundBlurDrawable mBackgroundBlurDrawable;
/**
* Temporary holder for a window background when it is set before {@link #mWindow} is
@@ -280,9 +282,14 @@
private final Paint mLegacyNavigationBarBackgroundPaint = new Paint();
private Insets mBackgroundInsets = Insets.NONE;
private Insets mLastBackgroundInsets = Insets.NONE;
+ private int mLastBackgroundBlurRadius = 0;
private boolean mDrawLegacyNavigationBarBackground;
private PendingInsetsController mPendingInsetsController = new PendingInsetsController();
+ private final ViewTreeObserver.OnPreDrawListener mBackgroundBlurOnPreDrawListener = () -> {
+ updateBackgroundBlur();
+ return true;
+ };
DecorView(Context context, int featureId, PhoneWindow window,
WindowManager.LayoutParams params) {
@@ -1263,18 +1270,27 @@
if (mBackgroundInsets == null) {
mBackgroundInsets = Insets.NONE;
}
+
if (mBackgroundInsets.equals(mLastBackgroundInsets)
+ && mWindow.mBackgroundBlurRadius == mLastBackgroundBlurRadius
&& mLastOriginalBackgroundDrawable == mOriginalBackgroundDrawable) {
return;
}
- if (mOriginalBackgroundDrawable == null || mBackgroundInsets.equals(Insets.NONE)) {
- // Call super since we are intercepting setBackground on this class.
- super.setBackgroundDrawable(mOriginalBackgroundDrawable);
- } else {
+ Drawable destDrawable = mOriginalBackgroundDrawable;
+ if (mWindow.mBackgroundBlurRadius > 0 && getViewRootImpl() != null
+ && mWindow.isTranslucent()) {
+ if (mBackgroundBlurDrawable == null) {
+ mBackgroundBlurDrawable = getViewRootImpl().createBackgroundBlurDrawable();
+ }
+ destDrawable = new LayerDrawable(new Drawable[] {mBackgroundBlurDrawable,
+ mOriginalBackgroundDrawable});
+ mLastBackgroundBlurRadius = mWindow.mBackgroundBlurRadius;
+ }
- // Call super since we are intercepting setBackground on this class.
- super.setBackgroundDrawable(new InsetDrawable(mOriginalBackgroundDrawable,
+
+ if (destDrawable != null && !mBackgroundInsets.equals(Insets.NONE)) {
+ destDrawable = new InsetDrawable(destDrawable,
mBackgroundInsets.left, mBackgroundInsets.top,
mBackgroundInsets.right, mBackgroundInsets.bottom) {
@@ -1286,12 +1302,32 @@
public boolean getPadding(Rect padding) {
return getDrawable().getPadding(padding);
}
- });
+ };
}
+
+ // Call super since we are intercepting setBackground on this class.
+ super.setBackgroundDrawable(destDrawable);
+
mLastBackgroundInsets = mBackgroundInsets;
mLastOriginalBackgroundDrawable = mOriginalBackgroundDrawable;
}
+ private void updateBackgroundBlur() {
+ if (mBackgroundBlurDrawable == null) return;
+
+ // If the blur radius is 0, the blur region won't be sent to surface flinger, so we don't
+ // need to calculate the corner radius.
+ if (mWindow.mBackgroundBlurRadius > 0) {
+ if (mOriginalBackgroundDrawable != null) {
+ final Outline outline = new Outline();
+ mOriginalBackgroundDrawable.getOutline(outline);
+ mBackgroundBlurDrawable.setCornerRadius(outline.mMode == Outline.MODE_ROUND_RECT
+ ? outline.getRadius() : 0);
+ }
+ }
+ mBackgroundBlurDrawable.setBlurRadius(mWindow.mBackgroundBlurRadius);
+ }
+
@Override
public Drawable getBackground() {
return mOriginalBackgroundDrawable;
@@ -1722,6 +1758,9 @@
cb.onAttachedToWindow();
}
+ getViewTreeObserver().addOnPreDrawListener(mBackgroundBlurOnPreDrawListener);
+ updateBackgroundDrawable();
+
if (mFeatureId == -1) {
/*
* The main window has been attached, try to restore any panels
@@ -1755,6 +1794,8 @@
cb.onDetachedFromWindow();
}
+ getViewTreeObserver().removeOnPreDrawListener(mBackgroundBlurOnPreDrawListener);
+
if (mWindow.mDecorContentParent != null) {
mWindow.mDecorContentParent.dismissPopups();
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 5df175e..d06413c 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -258,6 +258,8 @@
Drawable mBackgroundDrawable = null;
Drawable mBackgroundFallbackDrawable = null;
+ int mBackgroundBlurRadius = 0;
+
private boolean mLoadElevation = true;
private float mElevation;
@@ -1523,6 +1525,15 @@
}
@Override
+ public final void setBackgroundBlurRadius(int blurRadius) {
+ super.setBackgroundBlurRadius(blurRadius);
+ if (getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_CROSS_LAYER_BLUR)) {
+ mBackgroundBlurRadius = Math.max(blurRadius, 0);
+ }
+ }
+
+ @Override
public final void setFeatureDrawableResource(int featureId, int resId) {
if (resId != 0) {
DrawableFeatureState st = getDrawableState(featureId, true);
@@ -2549,6 +2560,9 @@
android.R.styleable.Window_windowBlurBehindRadius, 0);
}
+ setBackgroundBlurRadius(a.getDimensionPixelSize(
+ R.styleable.Window_windowBackgroundBlurRadius, 0));
+
if (params.windowAnimations == 0) {
params.windowAnimations = a.getResourceId(
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
new file mode 100644
index 0000000..aab054f
--- /dev/null
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package com.android.server.vibrator;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+message OneShotProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated int32 duration = 1;
+ repeated int32 amplitude = 2;
+}
+
+message WaveformProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated int32 timings = 1;
+ repeated int32 amplitudes = 2;
+ required bool repeat = 3;
+}
+
+message PrebakedProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional int32 effect_id = 1;
+ optional int32 effect_strength = 2;
+ optional int32 fallback = 3;
+}
+
+message ComposedProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated int32 effect_ids = 1;
+ repeated float effect_scales = 2;
+ repeated int32 delays = 3;
+}
+
+// A com.android.os.VibrationEffect object.
+message VibrationEffectProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional OneShotProto oneshot = 1;
+ optional WaveformProto waveform = 2;
+ optional PrebakedProto prebaked = 3;
+ optional ComposedProto composed = 4;
+}
+
+message SyncVibrationEffectProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated VibrationEffectProto effects = 1;
+ repeated int32 vibrator_ids = 2;
+}
+
+// A com.android.os.CombinedVibrationEffect object.
+message CombinedVibrationEffectProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated SyncVibrationEffectProto effects = 1;
+ repeated int32 delays = 2;
+}
+
+message VibrationAttributesProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional int32 usage = 1;
+ optional int32 audio_usage = 2;
+ optional int32 flags = 3;
+}
+
+// Next id: 7
+message VibrationProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional int64 start_time = 1;
+ optional int64 end_time = 2;
+ optional CombinedVibrationEffectProto effect = 3;
+ optional CombinedVibrationEffectProto original_effect = 4;
+ optional VibrationAttributesProto attributes = 5;
+ optional int32 status = 6;
+}
+
+// Next id: 18
+message VibratorManagerServiceDumpProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated int32 vibrator_ids = 1;
+ optional VibrationProto current_vibration = 2;
+ optional bool is_vibrating = 3;
+ optional VibrationProto current_external_vibration = 4;
+ optional bool vibrator_under_external_control = 5;
+ optional bool low_power_mode = 6;
+ optional int32 haptic_feedback_intensity = 7;
+ optional int32 haptic_feedback_default_intensity = 8;
+ optional int32 notification_intensity = 9;
+ optional int32 notification_default_intensity = 10;
+ optional int32 ring_intensity = 11;
+ optional int32 ring_default_intensity = 12;
+ repeated VibrationProto previous_ring_vibrations = 13;
+ repeated VibrationProto previous_notification_vibrations = 14;
+ repeated VibrationProto previous_alarm_vibrations = 15;
+ repeated VibrationProto previous_vibrations = 16;
+ repeated VibrationProto previous_external_vibrations = 17;
+}
\ No newline at end of file
diff --git a/core/proto/android/server/vibratorservice.proto b/core/proto/android/server/vibratorservice.proto
deleted file mode 100644
index 9e42e9e..0000000
--- a/core/proto/android/server/vibratorservice.proto
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.
- */
-
-syntax = "proto2";
-package com.android.server;
-
-option java_multiple_files = true;
-
-import "frameworks/base/core/proto/android/privacy.proto";
-
-message OneShotProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- repeated int32 duration = 1;
- repeated int32 amplitude = 2;
-}
-
-message WaveformProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- repeated int32 timings = 1;
- repeated int32 amplitudes = 2;
- required bool repeat = 3;
-}
-
-message PrebakedProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional int32 effect_id = 1;
- optional int32 effect_strength = 2;
- optional int32 fallback = 3;
-}
-
-message ComposedProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- repeated int32 effect_ids = 1;
- repeated float effect_scales = 2;
- repeated int32 delays = 3;
-}
-
-// A com.android.os.VibrationEffect object.
-message VibrationEffectProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional OneShotProto oneshot = 3;
- optional WaveformProto waveform = 1;
- optional PrebakedProto prebaked = 2;
- optional ComposedProto composed = 4;
-}
-
-message VibrationAttributesProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional int32 usage = 1;
- optional int32 audio_usage = 2;
- optional int32 flags = 3;
-}
-
-// Next id: 7
-message VibrationProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional int64 start_time = 1;
- optional int64 end_time = 4;
- optional VibrationEffectProto effect = 2;
- optional VibrationEffectProto original_effect = 3;
- optional VibrationAttributesProto attributes = 5;
- optional int32 status = 6;
-}
-
-// Next id: 17
-message VibratorServiceDumpProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional VibrationProto current_vibration = 1;
- optional bool is_vibrating = 2;
- optional VibrationProto current_external_vibration = 3;
- optional bool vibrator_under_external_control = 4;
- optional bool low_power_mode = 5;
- optional int32 haptic_feedback_intensity = 6;
- optional int32 haptic_feedback_default_intensity = 14;
- optional int32 notification_intensity = 7;
- optional int32 notification_default_intensity = 15;
- optional int32 ring_intensity = 8;
- optional int32 ring_default_intensity = 16;
- repeated VibrationProto previous_ring_vibrations = 9;
- repeated VibrationProto previous_notification_vibrations = 10;
- repeated VibrationProto previous_alarm_vibrations = 11;
- repeated VibrationProto previous_vibrations = 12;
- repeated VibrationProto previous_external_vibrations = 13;
-}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 201c529..ba43ee7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5471,6 +5471,13 @@
<permission android:name="android.permission.MANAGE_GAME_MODE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager
+ when they are performing reboot-blocking work.
+ @hide -->
+ <permission android:name="android.permission.SIGNAL_REBOOT_READINESS"
+ android:protectionLevel="signature|privileged" />
+
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
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 0000000..513da5e
--- /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 aeb4fc468..927a8b4 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -362,6 +362,12 @@
surface when the app has not drawn any content into this area. One example is
when the user is resizing a window of an activity in multi-window mode. -->
<attr name="windowBackgroundFallback" format="reference|color" />
+ <!-- Blur the screen behind the window with the bounds of the window.
+ The radius defines the size of the neighbouring area, from which pixels will be
+ averaged to form the final color for each pixel in the region.
+ A radius of 0 means no blur. The higher the radius, the denser the blur.
+ Corresponds to {@link android.view.Window#setBackgroundBlurRadius}. -->
+ <attr name="windowBackgroundBlurRadius" format="dimension" />
<!-- Drawable to use as a frame around the window. -->
<attr name="windowFrame" format="reference" />
<!-- Flag indicating whether there should be no title on this window. -->
@@ -1966,6 +1972,7 @@
<declare-styleable name="Window">
<attr name="windowBackground" />
<attr name="windowBackgroundFallback" />
+ <attr name="windowBackgroundBlurRadius" />
<attr name="windowContentOverlay" />
<attr name="windowFrame" />
<attr name="windowNoTitle" />
@@ -2255,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. -->
@@ -9253,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 068987e..278708a 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3065,6 +3065,12 @@
<public name="clipToOutline" />
<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 4109d4c..f66d33b 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 049ba23..87ae162 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -167,6 +167,9 @@
<!-- 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/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index 1a28b73..9a9b474 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -2,8 +2,11 @@
per-file BrightnessLimit.java = michaelwr@google.com, santoscordon@google.com
# Haptics
+per-file CombinedVibrationEffectTest.java = michaelwr@google.com
per-file ExternalVibrationTest.java = michaelwr@google.com
per-file VibrationEffectTest.java = michaelwr@google.com
+per-file VibratorInfoTest.java = michaelwr@google.com
+per-file VibratorTest.java = michaelwr@google.com
# Power
per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0484a9a..403dc65 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -474,6 +474,8 @@
<!-- Permission required for GTS test - GtsAssistIntentTestCases -->
<permission name="android.permission.MANAGE_SOUND_TRIGGER" />
<permission name="android.permission.CAPTURE_AUDIO_HOTWORD" />
+ <!-- Permission required for CTS test - CtsRebootReadinessTestCases -->
+ <permission name="android.permission.SIGNAL_REBOOT_READINESS" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 69c30f3..ded4a27 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 13f1fdd..2419865 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 034e65c..cb2aada 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 b22f358..0372417 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 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.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 @@
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 @@
}
@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 f3f2fc3..45d5515 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.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 @@
// 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 int mIconSize;
+ private final int mMaxIconAnimationDuration;
- SplashscreenContentDrawer(Context context) {
+ private int mIconSize;
+ private int mBrandingImageWidth;
+ private int mBrandingImageHeight;
+
+ 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 @@
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();
- } else {
- themeBGDrawable = context.getDrawable(resId);
}
- final Drawable iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
- : context.getPackageManager().getDefaultActivityIcon();
+ final int animationDuration;
+ final Drawable iconDrawable;
+ if (attrs.mReplaceIcon != null) {
+ iconDrawable = attrs.mReplaceIcon;
+ animationDuration = Math.max(0,
+ Math.min(attrs.mAnimationDuration, mMaxIconAnimationDuration));
+ } else {
+ iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
+ : context.getPackageManager().getDefaultActivityIcon();
+ animationDuration = 0;
+ }
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
}
-
- 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 f374922..5332291 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.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 @@
* 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 @@
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 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 @@
// 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 @@
}
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 @@
}
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 @@
+ 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 @@
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 @@
removeWindowSynced(taskId);
}
- protected void postAddWindow(int taskId, IBinder appToken,
- View view, WindowManager wm, WindowManager.LayoutParams params) {
- boolean shouldSaveView = true;
- try {
- wm.addView(view, params);
- } catch (WindowManager.BadTokenException e) {
- // ignore
- Slog.w(TAG, appToken + " already running, starting window not displayed. "
- + e.getMessage());
- shouldSaveView = false;
- } catch (RuntimeException e) {
- // don't crash if something else bad happens, for example a
- // failure loading resources because we are loading from an app
- // on external storage that has been unmounted.
- Slog.w(TAG, appToken + " failed creating starting window", e);
- shouldSaveView = false;
- } finally {
- if (view != null && view.getParent() == null) {
- Slog.w(TAG, "view not successfully added to wm, removing view");
- wm.removeViewImmediate(view);
- shouldSaveView = false;
- }
+ /**
+ * Called when the Task wants to copy the splash screen.
+ * @param taskId
+ */
+ public void copySplashScreenView(int taskId) {
+ final StartingWindowRecord preView = mStartingWindowRecords.get(taskId);
+ SplashScreenViewParcelable parcelable;
+ if (preView != null && preView.mContentView != null
+ && preView.mContentView.isCopyable()) {
+ parcelable = new SplashScreenViewParcelable(preView.mContentView);
+ } else {
+ parcelable = null;
}
+ if (DEBUG_SPLASH_SCREEN) {
+ Slog.v(TAG, "Copying splash screen window view for task: " + taskId
+ + " parcelable? " + parcelable);
+ }
+ ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
+ }
- if (shouldSaveView) {
- removeWindowSynced(taskId);
- mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
- final StartingWindowRecord tView =
- new StartingWindowRecord(view, null /* TaskSnapshotWindow */);
- mStartingWindowRecords.put(taskId, tView);
- }
+ protected void postAddWindow(int taskId, IBinder appToken,
+ View view, WindowManager wm, WindowManager.LayoutParams params,
+ SplashScreenView splashScreenView) {
+ mMainExecutor.execute(() -> {
+ boolean shouldSaveView = true;
+ try {
+ wm.addView(view, params);
+ } catch (WindowManager.BadTokenException e) {
+ // ignore
+ Slog.w(TAG, appToken + " already running, starting window not displayed. "
+ + e.getMessage());
+ shouldSaveView = false;
+ } catch (RuntimeException e) {
+ // don't crash if something else bad happens, for example a
+ // failure loading resources because we are loading from an app
+ // on external storage that has been unmounted.
+ Slog.w(TAG, appToken + " failed creating starting window", e);
+ shouldSaveView = false;
+ } finally {
+ if (view != null && view.getParent() == null) {
+ Slog.w(TAG, "view not successfully added to wm, removing view");
+ wm.removeViewImmediate(view);
+ shouldSaveView = false;
+ }
+ }
+ if (shouldSaveView) {
+ removeWindowSynced(taskId);
+ mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
+ final StartingWindowRecord tView = new StartingWindowRecord(view,
+ null /* TaskSnapshotWindow */, splashScreenView);
+ splashScreenView.startIntroAnimation();
+ mStartingWindowRecords.put(taskId, tView);
+ }
+ });
}
protected void removeWindowSynced(int taskId) {
@@ -459,10 +488,13 @@
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 176b33d..c815e65 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.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 @@
private Context mContext;
@Mock
private SizeCompatUIController mSizeCompatUI;
+ @Mock
+ private StartingSurfaceDrawer mStartingSurfaceDrawer;
ShellTaskOrganizer mOrganizer;
private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
@@ -112,7 +115,7 @@
.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 c9537af..de7d6c7 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.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 @@
@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 @@
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 @@
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/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp
index fa1752c..a48d7f7 100644
--- a/libs/hwui/jni/RenderEffect.cpp
+++ b/libs/hwui/jni/RenderEffect.cpp
@@ -64,8 +64,8 @@
sk_sp<SkImage> image = android::bitmap::toBitmap(bitmapHandle).makeImage();
SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- sk_sp<SkImageFilter> bitmapFilter =
- SkImageFilters::Image(image, srcRect, dstRect, kLow_SkFilterQuality);
+ sk_sp<SkImageFilter> bitmapFilter = SkImageFilters::Image(
+ image, srcRect, dstRect, SkSamplingOptions(SkFilterMode::kLinear));
return reinterpret_cast<jlong>(bitmapFilter.release());
}
@@ -150,4 +150,4 @@
android::RegisterMethodsOrDie(env, "android/graphics/RenderEffect",
gRenderEffectMethods, NELEM(gRenderEffectMethods));
return 0;
-}
\ No newline at end of file
+}
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index ef02a7e..92d7bf0 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -1117,7 +1117,7 @@
* @hide
*/
@Deprecated
- public List<String> getVpnLockdownWhitelist(int userId) {
+ public List<String> getVpnLockdownAllowlist(int userId) {
return getVpnManager().getVpnLockdownAllowlist(userId);
}
diff --git a/core/java/android/net/VpnTransportInfo.java b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
similarity index 88%
rename from core/java/android/net/VpnTransportInfo.java
rename to packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
index 082fa58..0242ba0 100644
--- a/core/java/android/net/VpnTransportInfo.java
+++ b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
@@ -16,8 +16,10 @@
package android.net;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
import android.annotation.NonNull;
-import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseArray;
@@ -26,7 +28,15 @@
import java.util.Objects;
-/** @hide */
+/**
+ * Container for VPN-specific transport information.
+ *
+ * @see android.net.TransportInfo
+ * @see NetworkCapabilities#getTransportInfo()
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
public final class VpnTransportInfo implements TransportInfo, Parcelable {
private static final SparseArray<String> sTypeToString =
MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"});
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 1566b76..35a4e81 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -402,6 +402,9 @@
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<uses-permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE" />
+ <!-- Permission required for CTS test - CtsRebootReadinessTestCases -->
+ <uses-permission android:name="android.permission.SIGNAL_REBOOT_READINESS" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d53c107..4b9fed2 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -755,7 +755,7 @@
+ Arrays.toString(vpnNai.declaredUnderlyingNetworks));
return;
}
- final NetworkAgentInfo underlyingNai = mService.getNetworkAgentInfoForNetwork(
+ final NetworkAgentInfo underlyingNai = mService.getNetworkAgentInfoForNetwork(
vpnNai.declaredUnderlyingNetworks[0]);
if (underlyingNai == null) return;
@@ -4910,12 +4910,15 @@
if (!mLockdownEnabled) {
return null;
}
- // The legacy lockdown VPN always only applies to UID 0.
+ // The legacy lockdown VPN always only applies to userId 0.
final NetworkAgentInfo nai = getVpnForUid(Process.FIRST_APPLICATION_UID);
if (nai == null || !isLegacyLockdownNai(nai)) return null;
// The legacy lockdown VPN must always have exactly one underlying network.
- if (nai.declaredUnderlyingNetworks == null || nai.declaredUnderlyingNetworks.length != 1) {
+ // This code may run on any thread and declaredUnderlyingNetworks may change, so store it in
+ // a local variable. There is no need to make a copy because its contents cannot change.
+ final Network[] underlying = nai.declaredUnderlyingNetworks;
+ if (underlying == null || underlying.length != 1) {
return null;
}
@@ -4925,8 +4928,7 @@
// Report that the VPN is not connected, so when the state of NetworkInfo objects
// overwritten by getLegacyLockdownState will be set to CONNECTING and not CONNECTED.
final NetworkAgentInfo defaultNetwork = getDefaultNetwork();
- if (defaultNetwork == null
- || !defaultNetwork.network.equals(nai.declaredUnderlyingNetworks[0])) {
+ if (defaultNetwork == null || !defaultNetwork.network.equals(underlying[0])) {
return null;
}
@@ -4985,6 +4987,7 @@
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ ensureRunningOnConnectivityServiceThread();
final String action = intent.getAction();
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index c6a8660..e12586b 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -1,8 +1,8 @@
# Connectivity / Networking
per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java = file:/services/core/java/com/android/server/net/OWNERS
-# Vibrator / Threads
-per-file VibratorManagerService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com
+# Threads
+per-file DisplayThread.java = michaelwr@google.com, ogunwale@google.com
# Zram writeback
per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 41903fc..67f6ec9 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -902,10 +902,13 @@
if (registeredObserver != null) {
Iterator<MonitoredPackage> it = failedPackages.iterator();
while (it.hasNext()) {
- VersionedPackage versionedPkg = it.next().mPackage;
- Slog.i(TAG, "Explicit health check failed for package " + versionedPkg);
- registeredObserver.execute(versionedPkg,
- PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1);
+ VersionedPackage versionedPkg = getVersionedPackage(it.next().getName());
+ if (versionedPkg != null) {
+ Slog.i(TAG,
+ "Explicit health check failed for package " + versionedPkg);
+ registeredObserver.execute(versionedPkg,
+ PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1);
+ }
}
}
}
@@ -1342,11 +1345,7 @@
MonitoredPackage newMonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) {
- VersionedPackage pkg = getVersionedPackage(name);
- if (pkg == null) {
- return null;
- }
- return new MonitoredPackage(pkg, durationMs, healthCheckDurationMs,
+ return new MonitoredPackage(name, durationMs, healthCheckDurationMs,
hasPassedHealthCheck, mitigationCalls);
}
@@ -1371,7 +1370,7 @@
* instances of this class.
*/
class MonitoredPackage {
- private final VersionedPackage mPackage;
+ private final String mPackageName;
// Times when package failures happen sorted in ascending order
@GuardedBy("mLock")
private final LongArrayQueue mFailureHistory = new LongArrayQueue();
@@ -1399,10 +1398,10 @@
@GuardedBy("mLock")
private long mHealthCheckDurationMs = Long.MAX_VALUE;
- MonitoredPackage(VersionedPackage pkg, long durationMs,
+ MonitoredPackage(String packageName, long durationMs,
long healthCheckDurationMs, boolean hasPassedHealthCheck,
LongArrayQueue mitigationCalls) {
- mPackage = pkg;
+ mPackageName = packageName;
mDurationMs = durationMs;
mHealthCheckDurationMs = healthCheckDurationMs;
mHasPassedHealthCheck = hasPassedHealthCheck;
@@ -1556,7 +1555,7 @@
/** Returns the monitored package name. */
private String getName() {
- return mPackage.getPackageName();
+ return mPackageName;
}
/**
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 1ad0176..2f98199 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -939,9 +939,12 @@
if (transcodeEnabled) {
LocalServices.getService(ActivityManagerInternal.class)
.registerAnrController((packageName, uid) -> {
- // TODO: Retrieve delay from ExternalStorageService that can check
- // transcoding status
- return SystemProperties.getInt("sys.fuse.transcode_anr_delay_ms", 0);
+ try {
+ return mStorageSessionController.getAnrDelayMillis(packageName, uid);
+ } catch (ExternalStorageServiceException e) {
+ Log.e(TAG, "Failed to get ANR delay for " + packageName, e);
+ return 0;
+ }
});
}
}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 7f638b9..915517a 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -19,6 +19,7 @@
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.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 @@
private NotificationManager mNotificationManager;
private StatusBarManager mStatusBarManager;
private WindowManagerInternal mWindowManager;
+ private ActivityTaskManagerInternal mActivityTaskManager;
private AlarmManager mAlarmManager;
private PowerManager mPowerManager;
@@ -366,6 +369,7 @@
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 @@
}
@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/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index a3d2002..3cc32be 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -16,7 +16,6 @@
package com.android.server.net;
-import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
import static android.provider.Settings.ACTION_VPN_SETTINGS;
@@ -34,7 +33,6 @@
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
import android.net.NetworkRequest;
import android.os.Handler;
import android.security.KeyStore;
@@ -45,7 +43,6 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
-import com.android.server.EventLogTags;
import com.android.server.connectivity.Vpn;
import java.util.List;
@@ -58,9 +55,6 @@
public class LockdownVpnTracker {
private static final String TAG = "LockdownVpnTracker";
- /** Number of VPN attempts before waiting for user intervention. */
- private static final int MAX_ERROR_COUNT = 4;
-
public static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
@NonNull private final Context mContext;
@@ -83,6 +77,7 @@
private Network mNetwork = null;
private LinkProperties mLinkProperties = null;
+ @Override
public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
boolean networkChanged = false;
if (!network.equals(mNetwork)) {
@@ -100,6 +95,7 @@
}
}
+ @Override
public void onLost(Network network) {
// The default network has gone down.
mNetwork = null;
@@ -134,8 +130,6 @@
@Nullable
private String mAcceptedEgressIface;
- private int mErrorCount;
-
public LockdownVpnTracker(@NonNull Context context,
@NonNull Handler handler,
@NonNull KeyStore keyStore,
@@ -165,7 +159,6 @@
*/
private void handleStateChangedLocked() {
final Network network = mDefaultNetworkCallback.getNetwork();
- final NetworkInfo egressInfo = mCm.getNetworkInfo(network); // Only for logging
final LinkProperties egressProp = mDefaultNetworkCallback.getLinkProperties();
final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
@@ -176,11 +169,9 @@
final boolean egressChanged = egressProp == null
|| !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
- final int egressType = (egressInfo == null) ? TYPE_NONE : egressInfo.getType();
final String egressIface = (egressProp == null) ?
null : egressProp.getInterfaceName();
- Log.d(TAG, "handleStateChanged: egress=" + egressType
- + " " + mAcceptedEgressIface + "->" + egressIface);
+ Log.d(TAG, "handleStateChanged: egress=" + mAcceptedEgressIface + "->" + egressIface);
if (egressDisconnected || egressChanged) {
mAcceptedEgressIface = null;
@@ -190,15 +181,6 @@
hideNotification();
return;
}
- if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
- EventLogTags.writeLockdownVpnError(egressType);
- }
-
- if (mErrorCount > MAX_ERROR_COUNT) {
- // Cannot happen because ConnectivityService never sees a NetworkInfo in state FAILED.
- showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
- return;
- }
// At this point, |network| is known to be non-null.
if (!vpnInfo.isConnectedOrConnecting()) {
@@ -209,7 +191,6 @@
}
Log.d(TAG, "Active network connected; starting VPN");
- EventLogTags.writeLockdownVpnConnecting(egressType);
showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
mAcceptedEgressIface = egressIface;
@@ -243,7 +224,6 @@
Log.d(TAG, "VPN connected using iface=" + iface
+ ", sourceAddr=" + sourceAddrs.toString());
- EventLogTags.writeLockdownVpnConnected(egressType);
showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
}
}
@@ -280,7 +260,6 @@
Log.d(TAG, "shutdownLocked()");
mAcceptedEgressIface = null;
- mErrorCount = 0;
mVpn.stopVpnRunnerPrivileged();
mVpn.setLockdown(false);
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index e91bb46..b06e84d 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 @@
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 9b092c0..bb4ec16 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 @@
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 @@
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 @@
String bitmapPath;
String iconUri;
final String locusIdString;
+ int splashScreenThemeResId;
int backupVersionCode;
ArraySet<String> categories = null;
ArrayList<Person> persons = new ArrayList<>();
@@ -1871,6 +1874,8 @@
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 @@
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 d3aace1..c06f01a 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 @@
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 @@
disabledMessageResId,
rank,
iconResId,
- enabled);
+ enabled,
+ splashScreenTheme);
} finally {
sa.recycle();
}
@@ -413,7 +416,7 @@
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 @@
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 863e3fe5..4d8abea 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 @@
}
@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/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index eb4a050..0087c0c 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -158,6 +158,29 @@
}
/**
+ * Called when {@code packageName} is about to ANR
+ *
+ * @return ANR dialog delay in milliseconds
+ */
+ public long getAnrDelayMillis(String packageName, int uid)
+ throws ExternalStorageServiceException {
+ synchronized (mLock) {
+ int size = mConnections.size();
+ for (int i = 0; i < size; i++) {
+ int key = mConnections.keyAt(i);
+ StorageUserConnection connection = mConnections.get(key);
+ if (connection != null) {
+ long delay = connection.getAnrDelayMillis(packageName, uid);
+ if (delay > 0) {
+ return delay;
+ }
+ }
+ }
+ }
+ return 0;
+ }
+
+ /**
* Removes and returns the {@link StorageUserConnection} for {@code vol}.
*
* Does nothing if {@link #shouldHandle} is {@code false}
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index 13cceee..709d558 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -16,6 +16,7 @@
package com.android.server.storage;
+import static android.service.storage.ExternalStorageService.EXTRA_ANR_TIMEOUT_MS;
import static android.service.storage.ExternalStorageService.EXTRA_ERROR;
import static android.service.storage.ExternalStorageService.FLAG_SESSION_ATTRIBUTE_INDEXABLE;
import static android.service.storage.ExternalStorageService.FLAG_SESSION_TYPE_FUSE;
@@ -143,6 +144,24 @@
}
/**
+ * Called when {@code packageName} is about to ANR
+ *
+ * @return ANR dialog delay in milliseconds
+ */
+ public long getAnrDelayMillis(String packageName, int uid)
+ throws ExternalStorageServiceException {
+ synchronized (mSessionsLock) {
+ for (String sessionId : mSessions.keySet()) {
+ long delay = mActiveConnection.getAnrDelayMillis(packageName, uid);
+ if (delay > 0) {
+ return delay;
+ }
+ }
+ }
+ return 0;
+ }
+
+ /**
* Removes a session without ending it or waiting for exit.
*
* This should only be used if the session has certainly been ended because the volume was
@@ -234,6 +253,9 @@
@GuardedBy("mLock")
private final ArrayList<CompletableFuture<Void>> mOutstandingOps = new ArrayList<>();
+ @GuardedBy("mLock")
+ private final ArrayList<CompletableFuture<Long>> mOutstandingTimeoutOps = new ArrayList<>();
+
@Override
public void close() {
ServiceConnection oldConnection = null;
@@ -250,6 +272,9 @@
for (CompletableFuture<Void> op : mOutstandingOps) {
op.cancel(true);
}
+ for (CompletableFuture<Long> op : mOutstandingTimeoutOps) {
+ op.cancel(true);
+ }
mOutstandingOps.clear();
}
@@ -264,27 +289,44 @@
}
}
- private void waitForAsync(AsyncStorageServiceCall asyncCall) throws Exception {
- CompletableFuture<IExternalStorageService> serviceFuture = connectIfNeeded();
+ private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall) throws Exception {
CompletableFuture<Void> opFuture = new CompletableFuture<>();
+ RemoteCallback callback = new RemoteCallback(result -> setResult(result, opFuture));
+
+ waitForAsync(asyncCall, callback, opFuture, mOutstandingOps,
+ DEFAULT_REMOTE_TIMEOUT_SECONDS);
+ }
+
+ private long waitForAsyncLong(AsyncStorageServiceCall asyncCall) throws Exception {
+ CompletableFuture<Long> opFuture = new CompletableFuture<>();
+ RemoteCallback callback =
+ new RemoteCallback(result -> setTimeoutResult(result, opFuture));
+
+ return waitForAsync(asyncCall, callback, opFuture, mOutstandingTimeoutOps,
+ 1 /* timeoutSeconds */);
+ }
+
+ private <T> T waitForAsync(AsyncStorageServiceCall asyncCall, RemoteCallback callback,
+ CompletableFuture<T> opFuture, ArrayList<CompletableFuture<T>> outstandingOps,
+ long timeoutSeconds) throws Exception {
+ CompletableFuture<IExternalStorageService> serviceFuture = connectIfNeeded();
try {
synchronized (mLock) {
- mOutstandingOps.add(opFuture);
+ outstandingOps.add(opFuture);
}
- serviceFuture.thenCompose(service -> {
+ return serviceFuture.thenCompose(service -> {
try {
- asyncCall.run(service,
- new RemoteCallback(result -> setResult(result, opFuture)));
+ asyncCall.run(service, callback);
} catch (RemoteException e) {
opFuture.completeExceptionally(e);
}
return opFuture;
- }).get(DEFAULT_REMOTE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }).get(timeoutSeconds, TimeUnit.SECONDS);
} finally {
synchronized (mLock) {
- mOutstandingOps.remove(opFuture);
+ outstandingOps.remove(opFuture);
}
}
}
@@ -292,9 +334,9 @@
public void startSession(Session session, ParcelFileDescriptor fd)
throws ExternalStorageServiceException {
try {
- waitForAsync((service, callback) -> service.startSession(session.sessionId,
+ waitForAsyncVoid((service, callback) -> service.startSession(session.sessionId,
FLAG_SESSION_TYPE_FUSE | FLAG_SESSION_ATTRIBUTE_INDEXABLE,
- fd, session.upperPath, session.lowerPath, callback));
+ fd, session.upperPath, session.lowerPath, callback));
} catch (Exception e) {
throw new ExternalStorageServiceException("Failed to start session: " + session, e);
} finally {
@@ -308,7 +350,7 @@
public void endSession(Session session) throws ExternalStorageServiceException {
try {
- waitForAsync((service, callback) ->
+ waitForAsyncVoid((service, callback) ->
service.endSession(session.sessionId, callback));
} catch (Exception e) {
throw new ExternalStorageServiceException("Failed to end session: " + session, e);
@@ -319,7 +361,7 @@
public void notifyVolumeStateChanged(String sessionId, StorageVolume vol) throws
ExternalStorageServiceException {
try {
- waitForAsync((service, callback) ->
+ waitForAsyncVoid((service, callback) ->
service.notifyVolumeStateChanged(sessionId, vol, callback));
} catch (Exception e) {
throw new ExternalStorageServiceException("Failed to notify volume state changed "
@@ -330,7 +372,7 @@
public void freeCache(String sessionId, String volumeUuid, long bytes)
throws ExternalStorageServiceException {
try {
- waitForAsync((service, callback) ->
+ waitForAsyncVoid((service, callback) ->
service.freeCache(sessionId, volumeUuid, bytes, callback));
} catch (Exception e) {
throw new ExternalStorageServiceException("Failed to free " + bytes
@@ -338,6 +380,27 @@
}
}
+ public long getAnrDelayMillis(String packgeName, int uid)
+ throws ExternalStorageServiceException {
+ try {
+ return waitForAsyncLong((service, callback) ->
+ service.getAnrDelayMillis(packgeName, uid, callback));
+ } catch (Exception e) {
+ throw new ExternalStorageServiceException("Failed to notify app not responding: "
+ + packgeName, e);
+ }
+ }
+
+ private void setTimeoutResult(Bundle result, CompletableFuture<Long> future) {
+ ParcelableException ex = result.getParcelable(EXTRA_ERROR);
+ if (ex != null) {
+ future.completeExceptionally(ex);
+ } else {
+ long timeoutMs = result.getLong(EXTRA_ANR_TIMEOUT_MS);
+ future.complete(timeoutMs);
+ }
+ }
+
private void setResult(Bundle result, CompletableFuture<Void> future) {
ParcelableException ex = result.getParcelable(EXTRA_ERROR);
if (ex != null) {
diff --git a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
index 3968723..685dce4 100644
--- a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
+++ b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
@@ -21,16 +21,14 @@
import android.os.CombinedVibrationEffect;
import android.os.Handler;
import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorManager;
import android.util.SparseArray;
import android.view.InputDevice;
import com.android.internal.annotations.GuardedBy;
-/** Delegates vibrations to all connected {@link InputDevice} with available {@link Vibrator}. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class InputDeviceDelegate implements InputManager.InputDeviceListener {
+/** Delegates vibrations to all connected {@link InputDevice} with one or more vibrators. */
+final class InputDeviceDelegate implements InputManager.InputDeviceListener {
private static final String TAG = "InputDeviceDelegate";
private final Object mLock = new Object();
@@ -38,7 +36,7 @@
private final InputManager mInputManager;
@GuardedBy("mLock")
- private final SparseArray<Vibrator> mInputDeviceVibrators = new SparseArray<>();
+ private final SparseArray<VibratorManager> mInputDeviceVibrators = new SparseArray<>();
/**
* Flag updated via {@link #updateInputDeviceVibrators(boolean)}, holding the value of {@link
@@ -47,7 +45,7 @@
@GuardedBy("mLock")
private boolean mShouldVibrateInputDevices;
- public InputDeviceDelegate(Context context, Handler handler) {
+ InputDeviceDelegate(Context context, Handler handler) {
mHandler = handler;
mInputManager = context.getSystemService(InputManager.class);
}
@@ -81,32 +79,22 @@
}
/**
- * Vibrate all {@link InputDevice} with {@link Vibrator} available using given effect.
+ * Vibrate all {@link InputDevice} with vibrators using given effect.
*
* @return {@link #isAvailable()}
*/
public boolean vibrateIfAvailable(int uid, String opPkg, CombinedVibrationEffect effect,
String reason, VibrationAttributes attrs) {
synchronized (mLock) {
- // TODO(b/159207608): Pass on the combined vibration once InputManager is merged
- if (effect instanceof CombinedVibrationEffect.Mono) {
- VibrationEffect e = ((CombinedVibrationEffect.Mono) effect).getEffect();
- if (e instanceof VibrationEffect.Prebaked) {
- VibrationEffect fallback = ((VibrationEffect.Prebaked) e).getFallbackEffect();
- if (fallback != null) {
- e = fallback;
- }
- }
- for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
- mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, e, reason, attrs);
- }
+ for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
+ mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, effect, reason, attrs);
}
return mInputDeviceVibrators.size() > 0;
}
}
/**
- * Cancel vibration on all {@link InputDevice} with {@link Vibrator} available.
+ * Cancel vibration on all {@link InputDevice} with vibrators.
*
* @return {@link #isAvailable()}
*/
@@ -147,9 +135,9 @@
if (device == null) {
continue;
}
- Vibrator vibrator = device.getVibrator();
- if (vibrator.hasVibrator()) {
- mInputDeviceVibrators.put(device.getId(), vibrator);
+ VibratorManager vibratorManager = device.getVibratorManager();
+ if (vibratorManager.getVibratorIds().length > 0) {
+ mInputDeviceVibrators.put(device.getId(), vibratorManager);
}
}
} else {
@@ -171,9 +159,9 @@
mInputDeviceVibrators.remove(deviceId);
return;
}
- Vibrator vibrator = device.getVibrator();
- if (vibrator.hasVibrator()) {
- mInputDeviceVibrators.put(deviceId, vibrator);
+ VibratorManager vibratorManager = device.getVibratorManager();
+ if (vibratorManager.getVibratorIds().length > 0) {
+ mInputDeviceVibrators.put(device.getId(), vibratorManager);
} else {
mInputDeviceVibrators.remove(deviceId);
}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index e0f5408..607da3c 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -25,25 +25,16 @@
import android.os.VibrationEffect;
import android.util.proto.ProtoOutputStream;
-import com.android.server.ComposedProto;
-import com.android.server.OneShotProto;
-import com.android.server.PrebakedProto;
-import com.android.server.VibrationAttributesProto;
-import com.android.server.VibrationEffectProto;
-import com.android.server.VibrationProto;
-import com.android.server.WaveformProto;
-
import java.text.SimpleDateFormat;
import java.util.Date;
/** Represents a vibration request to the vibrator service. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public class Vibration {
+final class Vibration {
private static final String TAG = "Vibration";
private static final SimpleDateFormat DEBUG_DATE_FORMAT =
new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
- public enum Status {
+ enum Status {
RUNNING,
FINISHED,
FORWARDED_TO_INPUT_DEVICES,
@@ -91,7 +82,7 @@
private long mEndTimeDebug;
private Status mStatus;
- public Vibration(IBinder token, int id, CombinedVibrationEffect effect,
+ Vibration(IBinder token, int id, CombinedVibrationEffect effect,
VibrationAttributes attrs, int uid, String opPkg, String reason) {
this.token = token;
this.mEffect = effect;
@@ -157,7 +148,7 @@
}
/** Debug information about vibrations. */
- public static final class DebugInfo {
+ static final class DebugInfo {
private final long mStartTimeDebug;
private final long mEndTimeDebug;
private final CombinedVibrationEffect mEffect;
@@ -169,7 +160,7 @@
private final String mReason;
private final Status mStatus;
- public DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibrationEffect effect,
+ DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibrationEffect effect,
CombinedVibrationEffect originalEffect, float scale, VibrationAttributes attrs,
int uid, String opPkg, String reason, Status status) {
mStartTimeDebug = startTimeDebug;
@@ -235,21 +226,49 @@
}
private void dumpEffect(
- ProtoOutputStream proto, long fieldId, CombinedVibrationEffect combinedEffect) {
- VibrationEffect effect;
- // TODO(b/177805090): add proper support for dumping combined effects to proto
- if (combinedEffect instanceof CombinedVibrationEffect.Mono) {
- effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect();
- } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) {
- effect = ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects().valueAt(0);
- } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) {
- dumpEffect(proto, fieldId,
- ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects().get(0));
- return;
- } else {
- // Unknown combined effect, skip dump.
- return;
+ ProtoOutputStream proto, long fieldId, CombinedVibrationEffect effect) {
+ dumpEffect(proto, fieldId,
+ (CombinedVibrationEffect.Sequential) CombinedVibrationEffect.startSequential()
+ .addNext(effect)
+ .combine());
+ }
+
+ private void dumpEffect(
+ ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Sequential effect) {
+ final long token = proto.start(fieldId);
+ for (int i = 0; i < effect.getEffects().size(); i++) {
+ CombinedVibrationEffect nestedEffect = effect.getEffects().get(i);
+ if (nestedEffect instanceof CombinedVibrationEffect.Mono) {
+ dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS,
+ (CombinedVibrationEffect.Mono) nestedEffect);
+ } else if (nestedEffect instanceof CombinedVibrationEffect.Stereo) {
+ dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS,
+ (CombinedVibrationEffect.Stereo) nestedEffect);
+ }
+ proto.write(CombinedVibrationEffectProto.DELAYS, effect.getDelays().get(i));
}
+ proto.end(token);
+ }
+
+ private void dumpEffect(
+ ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Mono effect) {
+ final long token = proto.start(fieldId);
+ dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffect());
+ proto.end(token);
+ }
+
+ private void dumpEffect(
+ ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Stereo effect) {
+ final long token = proto.start(fieldId);
+ for (int i = 0; i < effect.getEffects().size(); i++) {
+ proto.write(SyncVibrationEffectProto.VIBRATOR_IDS, effect.getEffects().keyAt(i));
+ dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffects().valueAt(i));
+ }
+ proto.end(token);
+ }
+
+ private void dumpEffect(
+ ProtoOutputStream proto, long fieldId, VibrationEffect effect) {
final long token = proto.start(fieldId);
if (effect instanceof VibrationEffect.OneShot) {
dumpEffect(proto, VibrationEffectProto.ONESHOT, (VibrationEffect.OneShot) effect);
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 0fa4fe1..10393f6 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -29,8 +29,7 @@
import java.util.Objects;
/** Controls vibration scaling. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibrationScaler {
+final class VibrationScaler {
private static final String TAG = "VibrationScaler";
// Scale levels. Each level, except MUTE, is defined as the delta between the current setting
@@ -56,7 +55,7 @@
private final VibrationSettings mSettingsController;
private final int mDefaultVibrationAmplitude;
- public VibrationScaler(Context context, VibrationSettings settingsController) {
+ VibrationScaler(Context context, VibrationSettings settingsController) {
mSettingsController = settingsController;
mDefaultVibrationAmplitude = context.getResources().getInteger(
com.android.internal.R.integer.config_defaultVibrationAmplitude);
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 8910bdf..334129d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -39,20 +39,18 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
-import com.android.server.VibratorServiceDumpProto;
import java.util.ArrayList;
import java.util.List;
/** Controls all the system settings related to vibration. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibrationSettings {
+final class VibrationSettings {
private static final String TAG = "VibrationSettings";
private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = {0, 30, 100, 30};
/** Listener for changes on vibration settings. */
- public interface OnVibratorSettingsChanged {
+ interface OnVibratorSettingsChanged {
/** Callback triggered when any of the vibrator settings change. */
void onChange();
}
@@ -86,7 +84,7 @@
@GuardedBy("mLock")
private boolean mLowPowerMode;
- public VibrationSettings(Context context, Handler handler) {
+ VibrationSettings(Context context, Handler handler) {
mContext = context;
mVibrator = context.getSystemService(Vibrator.class);
mAudioManager = context.getSystemService(AudioManager.class);
@@ -345,17 +343,17 @@
/** Write current settings into given {@link ProtoOutputStream}. */
public void dumpProto(ProtoOutputStream proto) {
synchronized (mLock) {
- proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
+ proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
mHapticFeedbackIntensity);
- proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
+ proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
mVibrator.getDefaultHapticFeedbackIntensity());
- proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY,
+ proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_INTENSITY,
mNotificationIntensity);
- proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
+ proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
mVibrator.getDefaultNotificationVibrationIntensity());
- proto.write(VibratorServiceDumpProto.RING_INTENSITY,
+ proto.write(VibratorManagerServiceDumpProto.RING_INTENSITY,
mRingIntensity);
- proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY,
+ proto.write(VibratorManagerServiceDumpProto.RING_DEFAULT_INTENSITY,
mVibrator.getDefaultRingVibrationIntensity());
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 04dac7c..3893267 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -42,8 +42,7 @@
import java.util.PriorityQueue;
/** Plays a {@link Vibration} in dedicated thread. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibrationThread extends Thread implements IBinder.DeathRecipient {
+final class VibrationThread extends Thread implements IBinder.DeathRecipient {
private static final String TAG = "VibrationThread";
private static final boolean DEBUG = false;
@@ -54,7 +53,7 @@
private static final long CALLBACKS_EXTRA_TIMEOUT = 100;
/** Callbacks for playing a {@link Vibration}. */
- public interface VibrationCallbacks {
+ interface VibrationCallbacks {
/**
* Callback triggered before starting a synchronized vibration step. This will be called
@@ -92,7 +91,7 @@
@GuardedBy("mLock")
private boolean mForceStop;
- public VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
+ VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
VibrationCallbacks callbacks) {
mVibration = vib;
@@ -169,7 +168,7 @@
}
}
- public Vibration getVibration() {
+ Vibration getVibration() {
return mVibration;
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 9dcf12c..95f6059 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -32,8 +32,7 @@
import libcore.util.NativeAllocationRegistry;
/** Controls a single vibrator. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibratorController {
+final class VibratorController {
private static final String TAG = "VibratorController";
private final Object mLock = new Object();
@@ -99,12 +98,12 @@
static native void vibratorAlwaysOnDisable(long nativePtr, long id);
- public VibratorController(int vibratorId, OnVibrationCompleteListener listener) {
+ VibratorController(int vibratorId, OnVibrationCompleteListener listener) {
this(vibratorId, listener, new NativeWrapper());
}
@VisibleForTesting
- public VibratorController(int vibratorId, OnVibrationCompleteListener listener,
+ VibratorController(int vibratorId, OnVibrationCompleteListener listener,
NativeWrapper nativeWrapper) {
mNativeWrapper = nativeWrapper;
mNativeWrapper.init(vibratorId, listener);
@@ -142,11 +141,6 @@
}
}
- @VisibleForTesting
- public NativeWrapper getNativeWrapper() {
- return mNativeWrapper;
- }
-
/** Return the {@link VibratorInfo} representing the vibrator controlled by this instance. */
public VibratorInfo getVibratorInfo() {
return mVibratorInfo;
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
similarity index 96%
rename from services/core/java/com/android/server/VibratorManagerService.java
rename to services/core/java/com/android/server/vibrator/VibratorManagerService.java
index d264f85..260d79a 100644
--- a/services/core/java/com/android/server/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.vibrator;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -56,12 +56,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
-import com.android.server.vibrator.InputDeviceDelegate;
-import com.android.server.vibrator.Vibration;
-import com.android.server.vibrator.VibrationScaler;
-import com.android.server.vibrator.VibrationSettings;
-import com.android.server.vibrator.VibrationThread;
-import com.android.server.vibrator.VibratorController;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
import libcore.util.NativeAllocationRegistry;
@@ -967,7 +963,7 @@
/** Listener for synced vibration completion callbacks from native. */
@VisibleForTesting
- public interface OnSyncedVibrationCompleteListener {
+ interface OnSyncedVibrationCompleteListener {
/** Callback triggered when synced vibration is complete. */
void onComplete(long vibrationId);
@@ -1167,6 +1163,7 @@
}
}
+ pw.println();
pw.println(" Previous external vibrations:");
for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
pw.println(" " + info);
@@ -1181,46 +1178,48 @@
mVibrationSettings.dumpProto(proto);
if (mCurrentVibration != null) {
mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
- VibratorServiceDumpProto.CURRENT_VIBRATION);
+ VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
}
if (mCurrentExternalVibration != null) {
mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
- VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
+ VibratorManagerServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
}
boolean isVibrating = false;
boolean isUnderExternalControl = false;
for (int i = 0; i < mVibrators.size(); i++) {
+ proto.write(VibratorManagerServiceDumpProto.VIBRATOR_IDS, mVibrators.keyAt(i));
isVibrating |= mVibrators.valueAt(i).isVibrating();
isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl();
}
- proto.write(VibratorServiceDumpProto.IS_VIBRATING, isVibrating);
- proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
+ proto.write(VibratorManagerServiceDumpProto.IS_VIBRATING, isVibrating);
+ proto.write(VibratorManagerServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
isUnderExternalControl);
- for (Vibration.DebugInfo info : mPreviousVibrations.get(
- VibrationAttributes.USAGE_RINGTONE)) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
- }
-
- for (Vibration.DebugInfo info : mPreviousVibrations.get(
- VibrationAttributes.USAGE_NOTIFICATION)) {
- info.dumpProto(proto,
- VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
- }
-
- for (Vibration.DebugInfo info : mPreviousVibrations.get(
- VibrationAttributes.USAGE_ALARM)) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
- }
-
- for (Vibration.DebugInfo info : mPreviousVibrations.get(
- VibrationAttributes.USAGE_UNKNOWN)) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
+ for (int i = 0; i < mPreviousVibrations.size(); i++) {
+ long fieldId;
+ switch (mPreviousVibrations.keyAt(i)) {
+ case VibrationAttributes.USAGE_RINGTONE:
+ fieldId = VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS;
+ break;
+ case VibrationAttributes.USAGE_NOTIFICATION:
+ fieldId = VibratorManagerServiceDumpProto
+ .PREVIOUS_NOTIFICATION_VIBRATIONS;
+ break;
+ case VibrationAttributes.USAGE_ALARM:
+ fieldId = VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS;
+ break;
+ default:
+ fieldId = VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS;
+ }
+ for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
+ info.dumpProto(proto, fieldId);
+ }
}
for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+ info.dumpProto(proto,
+ VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
}
}
proto.flush();
@@ -1300,7 +1299,7 @@
cancelingVibration.join();
} catch (InterruptedException e) {
Slog.w("Interrupted while waiting for vibration to finish before starting "
- + "external control", e);
+ + "external control", e);
}
}
if (DEBUG) {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 771b712..c63a0f0 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 @@
}
@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 @@
}
/**
+ * 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 68a2c5d..f440e56 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.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.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.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 @@
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 @@
if (_createTime > 0) {
createTime = _createTime;
}
+ mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);
}
/**
@@ -1807,7 +1836,85 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
// 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 @@
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 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 79f8229..37fda4c 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 @@
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 7d2075c..94379b1 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 @@
* @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 f16a646..2e98c2c 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.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 @@
/** The controller for all operations related to locktask. */
private LockTaskController mLockTaskController;
private ActivityStartController mActivityStartController;
+ PackageConfigPersister mPackageConfigPersister;
boolean mSuppressResizeConfigChanges;
@@ -866,6 +868,7 @@
setRecentTasks(new RecentTasks(this, mTaskSupervisor));
mVrController = new VrController(mGlobalLock);
mKeyguardController = mTaskSupervisor.getKeyguardController();
+ mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue);
}
public void onActivityManagerInternalAdded() {
@@ -2027,8 +2030,7 @@
// 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 @@
}
/**
+ * 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 @@
synchronized (mGlobalLock) {
mAppWarnings.onPackageUninstalled(name);
mCompatModePackages.handlePackageUninstalledLocked(name);
+ mPackageConfigPersister.onPackageUninstall(name);
}
}
@@ -6103,6 +6130,7 @@
public void removeUser(int userId) {
synchronized (mGlobalLock) {
mRootWindowContainer.removeUser(userId);
+ mPackageConfigPersister.removeUser(userId);
}
}
@@ -6200,6 +6228,8 @@
public void loadRecentTasksForUser(int userId) {
synchronized (mGlobalLock) {
mRecentTasks.loadUserRecentsLocked(userId);
+ // TODO renaming the methods(?)
+ mPackageConfigPersister.loadUserPackages(userId);
}
}
@@ -6308,5 +6338,54 @@
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 d90e885..efcaaa4 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 @@
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 0000000..1552a96
--- /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 ceebe95..6fbeaa4 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 @@
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 94e14dd..ef4a40f 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 @@
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 @@
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 9c8a997..8bd4dfd 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_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 @@
});
}
+ 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 @@
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 @@
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 @@
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 @@
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 9fac3f0..286d31e 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 @@
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 @@
});
}
+ 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 @@
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 @@
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 @@
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 264a3b4..c362023 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 @@
}
onConfigurationChanged(atm.getGlobalConfiguration());
+ mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mName);
}
public void setPid(int pid) {
@@ -802,6 +803,13 @@
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/core/jni/Android.bp b/services/core/jni/Android.bp
index a7abf6a..3012d73 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -54,7 +54,7 @@
"com_android_server_UsbMidiDevice.cpp",
"com_android_server_UsbHostManager.cpp",
"com_android_server_vibrator_VibratorController.cpp",
- "com_android_server_VibratorManagerService.cpp",
+ "com_android_server_vibrator_VibratorManagerService.cpp",
"com_android_server_PersistentDataBlockService.cpp",
"com_android_server_am_LowMemDetector.cpp",
"com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 9a8942b..d076434 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -1,10 +1,6 @@
# Display
per-file com_android_server_lights_LightsService.cpp = michaelwr@google.com, santoscordon@google.com
-# Haptics
-per-file com_android_server_vibrator_VibratorController.cpp = michaelwr@google.com
-per-file com_android_server_VibratorManagerService.cpp = michaelwr@google.com
-
# Input
per-file com_android_server_input_InputManagerService.cpp = michaelwr@google.com, svv@google.com
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index a6029cd..89b931d 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -29,7 +29,7 @@
#include <vibratorservice/VibratorHalController.h>
-#include "com_android_server_VibratorManagerService.h"
+#include "com_android_server_vibrator_VibratorManagerService.h"
namespace V1_0 = android::hardware::vibrator::V1_0;
namespace V1_1 = android::hardware::vibrator::V1_1;
@@ -73,7 +73,8 @@
static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK));
static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) {
- vibrator::ManagerHalController* manager = android_server_VibratorManagerService_getManager();
+ vibrator::ManagerHalController* manager =
+ android_server_vibrator_VibratorManagerService_getManager();
if (manager == nullptr) {
return nullptr;
}
diff --git a/services/core/jni/com_android_server_VibratorManagerService.cpp b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
similarity index 89%
rename from services/core/jni/com_android_server_VibratorManagerService.cpp
rename to services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
index 5dbb71a..a47ab9d 100644
--- a/services/core/jni/com_android_server_VibratorManagerService.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
@@ -26,7 +26,7 @@
#include <vibratorservice/VibratorManagerHalController.h>
-#include "com_android_server_VibratorManagerService.h"
+#include "com_android_server_vibrator_VibratorManagerService.h"
namespace android {
@@ -64,7 +64,7 @@
const jobject mCallbackListener;
};
-vibrator::ManagerHalController* android_server_VibratorManagerService_getManager() {
+vibrator::ManagerHalController* android_server_vibrator_VibratorManagerService_getManager() {
std::lock_guard<std::mutex> lock(gManagerMutex);
return gManager;
}
@@ -156,10 +156,11 @@
service->hal()->cancelSynced();
}
+inline static constexpr auto sNativeInitMethodSignature =
+ "(Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;)J";
+
static const JNINativeMethod method_table[] = {
- {"nativeInit",
- "(Lcom/android/server/VibratorManagerService$OnSyncedVibrationCompleteListener;)J",
- (void*)nativeInit},
+ {"nativeInit", sNativeInitMethodSignature, (void*)nativeInit},
{"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
{"nativeGetCapabilities", "(J)J", (void*)nativeGetCapabilities},
{"nativeGetVibratorIds", "(J)[I", (void*)nativeGetVibratorIds},
@@ -168,15 +169,15 @@
{"nativeCancelSynced", "(J)V", (void*)nativeCancelSynced},
};
-int register_android_server_VibratorManagerService(JavaVM* jvm, JNIEnv* env) {
+int register_android_server_vibrator_VibratorManagerService(JavaVM* jvm, JNIEnv* env) {
sJvm = jvm;
auto listenerClassName =
- "com/android/server/VibratorManagerService$OnSyncedVibrationCompleteListener";
+ "com/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener";
jclass listenerClass = FindClassOrDie(env, listenerClassName);
sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(J)V");
- return jniRegisterNativeMethods(env, "com/android/server/VibratorManagerService", method_table,
- NELEM(method_table));
+ return jniRegisterNativeMethods(env, "com/android/server/vibrator/VibratorManagerService",
+ method_table, NELEM(method_table));
}
}; // namespace android
diff --git a/services/core/jni/com_android_server_VibratorManagerService.h b/services/core/jni/com_android_server_vibrator_VibratorManagerService.h
similarity index 84%
rename from services/core/jni/com_android_server_VibratorManagerService.h
rename to services/core/jni/com_android_server_vibrator_VibratorManagerService.h
index 22950c5..9924e24 100644
--- a/services/core/jni/com_android_server_VibratorManagerService.h
+++ b/services/core/jni/com_android_server_vibrator_VibratorManagerService.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -21,7 +21,7 @@
namespace android {
-extern vibrator::ManagerHalController* android_server_VibratorManagerService_getManager();
+extern vibrator::ManagerHalController* android_server_vibrator_VibratorManagerService_getManager();
} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 34f6048..24221e7 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -39,7 +39,7 @@
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_vr_VrManagerService(JNIEnv* env);
int register_android_server_vibrator_VibratorController(JavaVM* vm, JNIEnv* env);
-int register_android_server_VibratorManagerService(JavaVM* vm, JNIEnv* env);
+int register_android_server_vibrator_VibratorManagerService(JavaVM* vm, JNIEnv* env);
int register_android_server_location_GnssLocationProvider(JNIEnv* env);
int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);
int register_android_server_tv_TvUinputBridge(JNIEnv* env);
@@ -54,10 +54,8 @@
int register_android_server_security_VerityUtils(JNIEnv* env);
int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
-int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
- JNIEnv* env);
-int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
- JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(JNIEnv* env);
int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
int register_android_server_AdbDebuggingManager(JNIEnv* env);
int register_android_server_FaceService(JNIEnv* env);
@@ -90,7 +88,7 @@
register_android_server_UsbHostManager(env);
register_android_server_vr_VrManagerService(env);
register_android_server_vibrator_VibratorController(vm, env);
- register_android_server_VibratorManagerService(vm, env);
+ register_android_server_vibrator_VibratorManagerService(vm, env);
register_android_server_SystemServer(env);
register_android_server_location_GnssLocationProvider(env);
register_android_server_devicepolicy_CryptoTestHelper(env);
@@ -109,10 +107,8 @@
register_android_server_security_VerityUtils(env);
register_android_server_am_CachedAppOptimizer(env);
register_android_server_am_LowMemDetector(env);
- register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
- env);
- register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
- env);
+ register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);
+ register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(env);
register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
register_android_server_AdbDebuggingManager(env);
register_android_server_FaceService(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b063e67..9a692be7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -6372,7 +6372,7 @@
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
return mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist(
+ () -> mInjector.getConnectivityManager().getVpnLockdownAllowlist(
caller.getUserId()));
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3f47227..bd20464 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -195,6 +195,7 @@
import com.android.server.uri.UriGrantsManagerService;
import com.android.server.usage.UsageStatsService;
import com.android.server.utils.TimingsTraceAndSlog;
+import com.android.server.vibrator.VibratorManagerService;
import com.android.server.vr.VrManagerService;
import com.android.server.webkit.WebViewUpdateService;
import com.android.server.wm.ActivityTaskManagerService;
diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS
index 6561778..f1402ea 100644
--- a/services/tests/servicestests/src/com/android/server/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/OWNERS
@@ -3,5 +3,4 @@
per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS
per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
-per-file *Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
per-file GestureLauncherServiceTest.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 640d6e5..1c55072 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -45,7 +45,9 @@
import android.hardware.input.InputManagerInternal;
import android.os.Handler;
import android.os.IBinder;
+import android.os.MessageQueue;
import android.os.Process;
+import android.platform.test.annotations.Presubmit;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
@@ -76,10 +78,15 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.time.Duration;
+import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.stream.LongStream;
@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class DisplayManagerServiceTest {
private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
@@ -193,8 +200,8 @@
verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
List<DisplayViewport> viewports = viewportCaptor.getValue();
- // Expect to receive 2 viewports: internal, and virtual
- assertEquals(2, viewports.size());
+ // Expect to receive at least 2 viewports: at least 1 internal, and 1 virtual
+ assertTrue(viewports.size() >= 2);
DisplayViewport virtualViewport = null;
DisplayViewport internalViewport = null;
@@ -202,7 +209,10 @@
DisplayViewport v = viewports.get(i);
switch (v.type) {
case DisplayViewport.VIEWPORT_INTERNAL: {
+ // If more than one internal viewport, this will get overwritten several times,
+ // which for the purposes of this test is fine.
internalViewport = v;
+ assertTrue(internalViewport.valid);
break;
}
case DisplayViewport.VIEWPORT_EXTERNAL: {
@@ -219,9 +229,6 @@
assertNotNull(internalViewport);
assertNotNull(virtualViewport);
- // INTERNAL
- assertTrue(internalViewport.valid);
-
// VIRTUAL
assertEquals(height, virtualViewport.deviceHeight);
assertEquals(width, virtualViewport.deviceWidth);
@@ -243,10 +250,12 @@
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
final int displayIds[] = bs.getDisplayIds();
- assertEquals(1, displayIds.length);
- final int displayId = displayIds[0];
- DisplayInfo info = bs.getDisplayInfo(displayId);
- assertEquals(info.type, Display.TYPE_INTERNAL);
+ final int size = displayIds.length;
+ assertTrue(size > 0);
+ for (int i = 0; i < size; i++) {
+ DisplayInfo info = bs.getDisplayInfo(displayIds[i]);
+ assertEquals(info.type, Display.TYPE_INTERNAL);
+ }
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -257,16 +266,22 @@
verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
List<DisplayViewport> viewports = viewportCaptor.getValue();
- // Expect to receive actual viewports: 1 internal
- assertEquals(1, viewports.size());
+ // Due to the nature of foldables, we may have a different number of viewports than
+ // displays, just verify there's at least one.
+ final int viewportSize = viewports.size();
+ assertTrue(viewportSize > 0);
- DisplayViewport internalViewport = viewports.get(0);
+ // Now verify that each viewport's displayId is valid.
+ Arrays.sort(displayIds);
+ for (int i = 0; i < viewportSize; i++) {
+ DisplayViewport internalViewport = viewports.get(i);
- // INTERNAL is the only one actual display.
- assertNotNull(internalViewport);
- assertEquals(DisplayViewport.VIEWPORT_INTERNAL, internalViewport.type);
- assertTrue(internalViewport.valid);
- assertEquals(displayId, internalViewport.displayId);
+ // INTERNAL is the only one actual display.
+ assertNotNull(internalViewport);
+ assertEquals(DisplayViewport.VIEWPORT_INTERNAL, internalViewport.type);
+ assertTrue(internalViewport.valid);
+ assertTrue(Arrays.binarySearch(displayIds, internalViewport.displayId) >= 0);
+ }
}
@Test
@@ -486,7 +501,6 @@
* Tests that collection of display color sampling results are sensible.
*/
@Test
- @FlakyTest(bugId = 172555744)
public void testDisplayedContentSampling() {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mShortMockedInjector);
@@ -937,8 +951,22 @@
// Would prefer to call displayManager.onStart() directly here but it performs binderService
// registration which triggers security exceptions when running from a test.
handler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS);
- // flush the handler
- handler.runWithScissors(() -> {}, 0 /* now */);
+ waitForIdleHandler(handler, Duration.ofSeconds(1));
+ }
+
+ private void waitForIdleHandler(Handler handler, Duration timeout) {
+ final MessageQueue queue = handler.getLooper().getQueue();
+ final CountDownLatch latch = new CountDownLatch(1);
+ queue.addIdleHandler(() -> {
+ latch.countDown();
+ // Remove idle handler
+ return false;
+ });
+ try {
+ latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail("Interrupted unexpectedly: " + e);
+ }
}
private class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 0f527f3..d5271ef 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.hdmi;
+import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
@@ -27,6 +28,7 @@
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioManager;
import android.os.Handler;
import android.os.IPowerManager;
import android.os.IThermalService;
@@ -66,6 +68,7 @@
private IPowerManager mIPowerManagerMock;
@Mock
private IThermalService mIThermalServiceMock;
+ @Mock private AudioManager mAudioManager;
@Before
public void setUp() {
@@ -101,11 +104,21 @@
}
@Override
+ boolean isPowerStandby() {
+ return false;
+ }
+
+ @Override
protected PowerManager getPowerManager() {
return powerManager;
}
@Override
+ AudioManager getAudioManager() {
+ return mAudioManager;
+ }
+
+ @Override
protected HdmiCecConfig getHdmiCecConfig() {
return hdmiCecConfig;
}
@@ -121,9 +134,11 @@
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDeviceTv);
- HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+ HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
hdmiPortInfos[0] =
new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+ hdmiPortInfos[1] =
+ new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
@@ -302,4 +317,171 @@
assertThat(features.contains(Constants.RC_PROFILE_TV_THREE)).isFalse();
assertThat(features.contains(Constants.RC_PROFILE_TV_FOUR)).isFalse();
}
+
+ @Test
+ public void startArcAction_enable_noAudioDevice() {
+ mHdmiCecLocalDeviceTv.startArcAction(true);
+
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+ }
+
+
+ @Test
+ public void startArcAction_disable_noAudioDevice() {
+ mHdmiCecLocalDeviceTv.startArcAction(false);
+
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+ }
+
+ @Test
+ public void startArcAction_enable_portDoesNotSupportArc() {
+ // Emulate Audio device on port 0x1000 (does not support ARC)
+ mNativeWrapper.setPortConnectionStatus(1, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+
+ mHdmiCecLocalDeviceTv.startArcAction(true);
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+ }
+
+ @Test
+ public void startArcAction_disable_portDoesNotSupportArc() {
+ // Emulate Audio device on port 0x1000 (does not support ARC)
+ mNativeWrapper.setPortConnectionStatus(1, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+
+ mHdmiCecLocalDeviceTv.startArcAction(false);
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+ }
+
+ @Test
+ public void startArcAction_enable_portSupportsArc() {
+ // Emulate Audio device on port 0x2000 (supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+ mTestLooper.dispatchAll();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+ }
+
+ @Test
+ public void startArcAction_disable_portSupportsArc() {
+ // Emulate Audio device on port 0x2000 (supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+ mTestLooper.dispatchAll();
+
+ mHdmiCecLocalDeviceTv.startArcAction(false);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+ }
+
+ @Test
+ public void handleInitiateArc_noAudioDevice() {
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+
+ mNativeWrapper.onCecMessage(requestArcInitiation);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+ }
+
+ @Test
+ public void handleInitiateArc_portDoesNotSupportArc() {
+ // Emulate Audio device on port 0x1000 (does not support ARC)
+ mNativeWrapper.setPortConnectionStatus(1, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+
+ mNativeWrapper.onCecMessage(requestArcInitiation);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+ }
+
+ @Test
+ public void handleInitiateArc_portSupportsArc() {
+ // Emulate Audio device on port 0x2000 (supports ARC)
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+
+ mNativeWrapper.onCecMessage(requestArcInitiation);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
index 72c40ea..014bfd2 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
@@ -23,7 +23,7 @@
import androidx.annotation.NonNull;
/** Fake implementation of {@link Vibrator} for service tests. */
-public final class FakeVibrator extends Vibrator {
+final class FakeVibrator extends Vibrator {
private int mDefaultHapticFeedbackIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM;
private int mDefaultNotificationIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index f562c16..4634e12 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -33,7 +33,7 @@
* Provides {@link VibratorController} with controlled vibrator hardware capabilities and
* interactions.
*/
-public final class FakeVibratorControllerProvider {
+final class FakeVibratorControllerProvider {
private static final int EFFECT_DURATION = 20;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index e71c2f5..8c62b7f 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -67,9 +67,8 @@
private static final String REASON = "some reason";
private static final VibrationAttributes VIBRATION_ATTRIBUTES =
new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
- private static final VibrationEffect EFFECT = VibrationEffect.createOneShot(100, 255);
private static final CombinedVibrationEffect SYNCED_EFFECT =
- CombinedVibrationEffect.createSynced(EFFECT);
+ CombinedVibrationEffect.createSynced(VibrationEffect.createOneShot(100, 255));
@Rule public MockitoRule rule = MockitoJUnit.rule();
@@ -105,6 +104,7 @@
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
assertFalse(mInputDeviceDelegate.isAvailable());
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
mInputDeviceDelegate.onInputDeviceAdded(1);
@@ -118,6 +118,7 @@
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertFalse(mInputDeviceDelegate.isAvailable());
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithoutVibrator(1));
updateInputDevices(new int[]{1});
@@ -132,6 +133,7 @@
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertFalse(mInputDeviceDelegate.isAvailable());
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
updateInputDevices(new int[]{1});
@@ -142,6 +144,7 @@
@Test
public void onInputDeviceChanged_withSettingsDisabled_ignoresDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
@@ -153,6 +156,7 @@
@Test
public void onInputDeviceChanged_deviceLosesVibrator_removesDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}, new int[0]);
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithVibrator(1), createInputDeviceWithoutVibrator(1));
@@ -167,6 +171,7 @@
@Test
public void onInputDeviceChanged_deviceLost_removesDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}, new int[0]);
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithVibrator(1), (InputDevice) null);
@@ -181,6 +186,7 @@
@Test
public void onInputDeviceChanged_deviceAddsVibrator_addsDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0], new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithoutVibrator(1), createInputDeviceWithVibrator(1));
@@ -195,8 +201,10 @@
@Test
public void onInputDeviceRemoved_removesDevice() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(
createInputDeviceWithoutVibrator(1));
+ when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
@@ -209,7 +217,9 @@
@Test
public void updateInputDeviceVibrators_usesFlagToLoadDeviceList() throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+ when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
@@ -223,6 +233,7 @@
public void updateInputDeviceVibrators_withDeviceWithoutVibrator_deviceIsIgnored()
throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
when(mIInputManagerMock.getInputDevice(eq(1)))
.thenReturn(createInputDeviceWithoutVibrator(1));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
@@ -240,14 +251,16 @@
public void vibrateIfAvailable_withInputDevices_returnsTrueAndVibratesAllDevices()
throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+ when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
assertTrue(mInputDeviceDelegate.vibrateIfAvailable(
UID, PACKAGE_NAME, SYNCED_EFFECT, REASON, VIBRATION_ATTRIBUTES));
- verify(mIInputManagerMock).vibrate(eq(1), same(EFFECT), any());
- verify(mIInputManagerMock).vibrate(eq(2), same(EFFECT), any());
+ verify(mIInputManagerMock).vibrateCombined(eq(1), same(SYNCED_EFFECT), any());
+ verify(mIInputManagerMock).vibrateCombined(eq(2), same(SYNCED_EFFECT), any());
}
@Test
@@ -261,7 +274,9 @@
public void cancelVibrateIfAvailable_withInputDevices_returnsTrueAndStopsAllDevices()
throws Exception {
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+ when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 3ff8e76..1b7e1ca 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -40,6 +40,7 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.test.TestLooper;
+import android.platform.test.annotations.LargeTest;
import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
@@ -709,6 +710,7 @@
assertEquals(Arrays.asList(6), mVibratorProviders.get(3).getAmplitudes());
}
+ @LargeTest
@Test
public void vibrate_withWaveform_totalVibrationTimeRespected() {
int totalDuration = 10_000; // 10s
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
rename to services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index da3d1d6..ba0a472 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.vibrator;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -73,9 +73,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.vibrator.FakeVibrator;
-import com.android.server.vibrator.FakeVibratorControllerProvider;
-import com.android.server.vibrator.VibratorController;
+import com.android.server.LocalServices;
import org.junit.After;
import org.junit.Before;
@@ -533,28 +531,15 @@
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
- when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
+ when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
+ when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
VibratorManagerService service = createService();
- // Prebaked vibration will play fallback waveform on input device.
- ArgumentCaptor<VibrationEffect> captor = ArgumentCaptor.forClass(VibrationEffect.class);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
- verify(mIInputManagerMock).vibrate(eq(1), captor.capture(), any());
- assertTrue(captor.getValue() instanceof VibrationEffect.Waveform);
-
- VibrationEffect[] effects = new VibrationEffect[]{
- VibrationEffect.createOneShot(100, 128),
- VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1),
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
- .compose(),
- };
-
- for (VibrationEffect effect : effects) {
- vibrate(service, effect, ALARM_ATTRS);
- verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
- }
+ CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
+ VibrationEffect.createOneShot(10, 10));
+ vibrate(service, effect, ALARM_ATTRS);
+ verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
// VibrationThread will start this vibration async, so wait before checking it never played.
assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(),
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 be489c3..5614aa2 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 @@
"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 72b8439..aa1110c 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 @@
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 77fca3d..9c16143 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 void removeStartingWindow(int taskId) { }
@Override
+ public void copySplashScreenView(int taskId) { }
+
+ @Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { }
@Override
@@ -609,10 +612,10 @@
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 @@
@Override
public void removeStartingWindow(int taskId) { }
-
+ @Override
+ public void copySplashScreenView(int taskId) { }
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { }
@@ -829,6 +833,8 @@
@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 83b30a9..c13d6b1 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 @@
public void removeStartingWindow(int taskId) {
}
@Override
+ public void copySplashScreenView(int taskId) {
+ }
+ @Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) {
}
@Override
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 401d87a..0508125 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -125,21 +125,11 @@
}
/**
- * Test rollbacks of staged installs involving only apks with bad update.
- * Trigger rollback phase.
- */
- @Test
- public void testBadApkOnly_Phase3_Crash() throws Exception {
- // One more crash to trigger rollback
- RollbackUtils.sendCrashBroadcast(TestApp.A, 1);
- }
-
- /**
* Test rollbacks of staged installs involving only apks.
* Confirm rollback phase.
*/
@Test
- public void testBadApkOnly_Phase4_VerifyRollback() throws Exception {
+ public void testBadApkOnly_Phase3_VerifyRollback() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
InstallUtils.processUserData(TestApp.A);
@@ -447,8 +437,10 @@
Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
Rollback.from(TestApp.A, 0).to(TestApp.A1));
- // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
- RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
+ // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT-1 times
+ RollbackUtils.sendCrashBroadcast(TestApp.A, 4);
+ // Sleep for a while to make sure we don't trigger rollback
+ Thread.sleep(TimeUnit.SECONDS.toMillis(30));
}
@Test
@@ -504,6 +496,45 @@
}
@Test
+ public void testWatchdogMonitorsAcrossReboots_Phase1_Install() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
+
+ Install.single(TestApp.ACrashing2).setEnableRollback().setStaged().commit();
+ }
+
+ @Test
+ public void testWatchdogMonitorsAcrossReboots_Phase2_VerifyInstall() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+ // Trigger rollback of test app.
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+ Integer.toString(5), false);
+
+ // The final crash that causes rollback will come from the host side.
+ RollbackUtils.sendCrashBroadcast(TestApp.A, 4);
+ }
+
+ @Test
+ public void testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback() {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
+
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TestApp.A);
+ assertThat(rollback).isNotNull();
+ assertThat(rollback).packagesContainsExactly(
+ Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(rollback).causePackagesContainsExactly(TestApp.ACrashing2);
+ assertThat(rollback).isStaged();
+ assertThat(rollback.getCommittedSessionId()).isNotEqualTo(-1);
+ }
+
+ @Test
public void hasMainlineModule() throws Exception {
String pkgName = getModuleMetadataPackageName();
boolean existed = InstrumentationRegistry.getInstrumentation().getContext()
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 1d5730f..65fb7b6c 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -153,13 +153,14 @@
getDevice().reboot();
runPhase("testBadApkOnly_Phase2_VerifyInstall");
- // Trigger rollback and wait for reboot to happen
- runPhase("testBadApkOnly_Phase3_Crash");
+ // Launch the app to crash to trigger rollback
+ startActivity(TESTAPP_A);
+ // Wait for reboot to happen
waitForDeviceNotAvailable(2, TimeUnit.MINUTES);
getDevice().waitForDeviceAvailable();
- runPhase("testBadApkOnly_Phase4_VerifyRollback");
+ runPhase("testBadApkOnly_Phase3_VerifyRollback");
assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_APP_CRASH, TESTAPP_A);
assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null);
@@ -304,8 +305,10 @@
getDevice().reboot();
// Verify apex was installed and then crash the apk
runPhase("testRollbackApexWithApkCrashing_Phase2_Crash");
- // Wait for crash to trigger rollback
- waitForDeviceNotAvailable(5, TimeUnit.MINUTES);
+ // Launch the app to crash to trigger rollback
+ startActivity(TESTAPP_A);
+ // Wait for reboot to happen
+ waitForDeviceNotAvailable(2, TimeUnit.MINUTES);
getDevice().waitForDeviceAvailable();
// Verify rollback occurred due to crash of apk-in-apex
runPhase("testRollbackApexWithApkCrashing_Phase3_VerifyRollback");
@@ -551,6 +554,30 @@
});
}
+ /**
+ * Tests that packages are monitored across multiple reboots.
+ */
+ @Test
+ public void testWatchdogMonitorsAcrossReboots() throws Exception {
+ runPhase("testWatchdogMonitorsAcrossReboots_Phase1_Install");
+
+ // The first reboot will make the rollback available.
+ // Information about which packages are monitored will be persisted to a file before the
+ // second reboot, and read from disk after the second reboot.
+ getDevice().reboot();
+ getDevice().reboot();
+
+ runPhase("testWatchdogMonitorsAcrossReboots_Phase2_VerifyInstall");
+
+ // Launch the app to crash to trigger rollback
+ startActivity(TESTAPP_A);
+ // Wait for reboot to happen
+ waitForDeviceNotAvailable(2, TimeUnit.MINUTES);
+ getDevice().waitForDeviceAvailable();
+
+ runPhase("testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback");
+ }
+
private void pushTestApex() throws Exception {
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
@@ -631,6 +658,12 @@
}
}
+ private void startActivity(String packageName) throws Exception {
+ String cmd = "am start -S -a android.intent.action.MAIN "
+ + "-c android.intent.category.LAUNCHER " + packageName;
+ getDevice().executeShellCommand(cmd);
+ }
+
private void crashProcess(String processName, int numberOfCrashes) throws Exception {
String pid = "";
String lastPid = "invalid";
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index ee04973..c5e76a9 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -1300,10 +1300,8 @@
}
private void processBroadcastForVpn(Intent intent) {
- // The BroadcastReceiver for this broadcast checks it is being run on the handler thread.
- final Handler handler = new Handler(mVMSHandlerThread.getLooper());
- handler.post(() -> mServiceContext.sendBroadcast(intent));
- HandlerUtils.waitForIdle(handler, TIMEOUT_MS);
+ mServiceContext.sendBroadcast(intent);
+ HandlerUtils.waitForIdle(mVMSHandlerThread, TIMEOUT_MS);
waitForIdle();
}
@@ -1430,6 +1428,7 @@
FakeSettingsProvider.clearSettingsProvider();
mServiceContext = new MockContext(InstrumentationRegistry.getContext(),
new FakeSettingsProvider());
+ mServiceContext.setUseRegisteredHandlers(true);
LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
LocalServices.addService(
NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class));
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
index 1102624..4a1f96d 100644
--- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
@@ -42,6 +42,8 @@
private final List<BroadcastInterceptor> mInterceptors = new ArrayList<>();
+ private boolean mUseRegisteredHandlers;
+
public abstract class FutureIntent extends FutureTask<Intent> {
public FutureIntent() {
super(
@@ -61,17 +63,24 @@
public class BroadcastInterceptor extends FutureIntent {
private final BroadcastReceiver mReceiver;
private final IntentFilter mFilter;
+ private final Handler mHandler;
- public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter) {
+ public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter,
+ Handler handler) {
mReceiver = receiver;
mFilter = filter;
+ mHandler = mUseRegisteredHandlers ? handler : null;
}
public boolean dispatchBroadcast(Intent intent) {
if (mFilter.match(getContentResolver(), intent, false, TAG) > 0) {
if (mReceiver != null) {
final Context context = BroadcastInterceptingContext.this;
- mReceiver.onReceive(context, intent);
+ if (mHandler == null) {
+ mReceiver.onReceive(context, intent);
+ } else {
+ mHandler.post(() -> mReceiver.onReceive(context, intent));
+ }
return false;
} else {
set(intent);
@@ -116,25 +125,38 @@
}
public FutureIntent nextBroadcastIntent(IntentFilter filter) {
- final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter);
+ final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter, null);
synchronized (mInterceptors) {
mInterceptors.add(interceptor);
}
return interceptor;
}
+ /**
+ * Whether to send broadcasts to registered handlers. By default, receivers are called
+ * synchronously by sendBroadcast. If this method is called with {@code true}, the receiver is
+ * instead called by a runnable posted to the Handler specified when the receiver was
+ * registered. This method applies only to future registrations, already-registered receivers
+ * are unaffected.
+ */
+ public void setUseRegisteredHandlers(boolean use) {
+ synchronized (mInterceptors) {
+ mUseRegisteredHandlers = use;
+ }
+ }
+
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
- synchronized (mInterceptors) {
- mInterceptors.add(new BroadcastInterceptor(receiver, filter));
- }
- return null;
+ return registerReceiver(receiver, filter, null, null);
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
- return registerReceiver(receiver, filter);
+ synchronized (mInterceptors) {
+ mInterceptors.add(new BroadcastInterceptor(receiver, filter, scheduler));
+ }
+ return null;
}
@Override