Merge "Update BatteryStatsImpl to handle multidisplay" into sc-v2-dev
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index b49bbc5..d4e3239 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -18,7 +18,6 @@
import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
import static android.os.Process.INVALID_UID;
-import android.Manifest;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
@@ -332,7 +331,6 @@
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -345,7 +343,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
List<AppSearchSchema> schemas = new ArrayList<>(schemaBundles.size());
@@ -424,7 +422,6 @@
Objects.requireNonNull(userHandle);
Objects.requireNonNull(callback);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -433,7 +430,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -460,7 +457,6 @@
Objects.requireNonNull(userHandle);
Objects.requireNonNull(callback);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -469,7 +465,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -499,7 +495,6 @@
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -512,7 +507,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchBatchResult.Builder<String, Void> resultBuilder =
@@ -589,7 +584,6 @@
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -602,7 +596,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchBatchResult.Builder<String, Bundle> resultBuilder =
@@ -674,7 +668,6 @@
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -687,7 +680,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
@@ -744,7 +737,6 @@
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -757,7 +749,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
@@ -813,7 +805,6 @@
Objects.requireNonNull(userHandle);
Objects.requireNonNull(callback);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -822,7 +813,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -846,7 +837,6 @@
Objects.requireNonNull(packageName);
Objects.requireNonNull(userHandle);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -855,7 +845,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -884,7 +874,6 @@
Objects.requireNonNull(userHandle);
Objects.requireNonNull(callback);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -893,7 +882,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -940,7 +929,6 @@
Objects.requireNonNull(userHandle);
Objects.requireNonNull(callback);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -949,7 +937,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -1006,7 +994,6 @@
Objects.requireNonNull(userHandle);
Objects.requireNonNull(callback);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -1015,7 +1002,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -1057,7 +1044,6 @@
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -1070,7 +1056,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchBatchResult.Builder<String, Void> resultBuilder =
@@ -1147,7 +1133,6 @@
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -1160,7 +1145,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
@@ -1215,7 +1200,6 @@
Objects.requireNonNull(userHandle);
Objects.requireNonNull(callback);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -1224,7 +1208,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -1249,7 +1233,6 @@
Objects.requireNonNull(userHandle);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -1262,7 +1245,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
@@ -1305,7 +1288,6 @@
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@@ -1319,7 +1301,7 @@
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
Context targetUserContext = mContext.createContextAsUser(targetUser,
@@ -1407,22 +1389,12 @@
/**
* Helper for dealing with incoming user arguments to system service calls.
*
- * <p>Takes care of checking permissions and if the target is special user, this method will
- * simply throw.
- *
* @param targetUserHandle The user which the caller is requesting to execute as.
- * @param callingPid The actual pid of the caller as determined by Binder.
* @param callingUid The actual uid of the caller as determined by Binder.
- *
* @return the user handle that the call should run as. Will always be a concrete user.
- *
- * @throws IllegalArgumentException if the target user is a special user.
- * @throws SecurityException if caller trying to interact across user without
- * {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL}
*/
@NonNull
- private UserHandle handleIncomingUser(@NonNull UserHandle targetUserHandle, int callingPid,
- int callingUid) {
+ private UserHandle handleIncomingUser(@NonNull UserHandle targetUserHandle, int callingUid) {
UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
if (callingUserHandle.equals(targetUserHandle)) {
return targetUserHandle;
@@ -1434,16 +1406,9 @@
"Call does not support special user " + targetUserHandle);
}
- if (mContext.checkPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- callingPid,
- callingUid) == PackageManager.PERMISSION_GRANTED) {
- return targetUserHandle;
- }
throw new SecurityException(
- "Permission denied while calling from uid " + callingUid
- + " with " + targetUserHandle + "; Requires permission: "
- + Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ "Requested user, " + targetUserHandle + ", is not the same as the calling user, "
+ + callingUserHandle + ".");
}
/**
diff --git a/core/api/current.txt b/core/api/current.txt
index a79df58..1cf7a4c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6712,6 +6712,7 @@
}
public class TaskInfo {
+ method public boolean isVisible();
field @Nullable public android.content.ComponentName baseActivity;
field @NonNull public android.content.Intent baseIntent;
field public boolean isRunning;
@@ -17949,7 +17950,7 @@
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> EDGE_AVAILABLE_EDGE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES;
- field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateOrientationMap> INFO_DEVICE_STATE_ORIENTATION_MAP;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SUPPORTED_HARDWARE_LEVEL;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.String> INFO_VERSION;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES;
@@ -18644,7 +18645,7 @@
method public android.util.Rational getElement(int, int);
}
- public final class DeviceStateOrientationMap {
+ public final class DeviceStateSensorOrientationMap {
method public int getSensorOrientation(long);
field public static final long FOLDED = 4L; // 0x4L
field public static final long NORMAL = 0L; // 0x0L
@@ -46912,15 +46913,15 @@
}
@UiThread public interface AttachedSurfaceControl {
- method public default void addOnSurfaceTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnSurfaceTransformHintChangedListener);
+ method public default void addOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
method public boolean applyTransactionOnDraw(@NonNull android.view.SurfaceControl.Transaction);
method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl);
- method public default int getSurfaceTransformHint();
- method public default void removeOnSurfaceTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnSurfaceTransformHintChangedListener);
+ method public default int getBufferTransformHint();
+ method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
}
- @UiThread public static interface AttachedSurfaceControl.OnSurfaceTransformHintChangedListener {
- method public void onSurfaceTransformHintChanged(int);
+ @UiThread public static interface AttachedSurfaceControl.OnBufferTransformHintChangedListener {
+ method public void onBufferTransformHintChanged(int);
}
public final class Choreographer {
@@ -48410,6 +48411,12 @@
method public void readFromParcel(android.os.Parcel);
method public void release();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int BUFFER_TRANSFORM_IDENTITY = 0; // 0x0
+ field public static final int BUFFER_TRANSFORM_MIRROR_HORIZONTAL = 1; // 0x1
+ field public static final int BUFFER_TRANSFORM_MIRROR_VERTICAL = 2; // 0x2
+ field public static final int BUFFER_TRANSFORM_ROTATE_180 = 3; // 0x3
+ field public static final int BUFFER_TRANSFORM_ROTATE_270 = 7; // 0x7
+ field public static final int BUFFER_TRANSFORM_ROTATE_90 = 4; // 0x4
field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl> CREATOR;
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1d406a5..5b27019 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5377,10 +5377,12 @@
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addOnHeadTrackingModeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadTrackingModeChangedListener);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnHeadToSoundstagePoseUpdatedListener();
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnSpatializerOutputChangedListener();
method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<android.media.AudioDeviceAttributes> getCompatibleAudioDevices();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getDesiredHeadTrackingMode();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void getEffectParameter(int, @NonNull byte[]);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getHeadTrackingMode();
+ method @IntRange(from=0) @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getOutput();
method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<java.lang.Integer> getSupportedHeadTrackingModes();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void recenterHeadTracker();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes);
@@ -5390,6 +5392,7 @@
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setEnabled(boolean);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setGlobalTransform(@NonNull float[]);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnHeadToSoundstagePoseUpdatedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadToSoundstagePoseUpdatedListener);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnSpatializerOutputChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerOutputChangedListener);
field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_DISABLED = -1; // 0xffffffff
field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_OTHER = 0; // 0x0
field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2; // 0x2
@@ -5406,6 +5409,10 @@
method public void onHeadTrackingModeChanged(@NonNull android.media.Spatializer, int);
}
+ public static interface Spatializer.OnSpatializerOutputChangedListener {
+ method public void onSpatializerOutputChanged(@NonNull android.media.Spatializer, @IntRange(from=0) int);
+ }
+
}
package android.media.audiofx {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 22091fc..f453ba1 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -77,7 +77,6 @@
import android.os.Looper;
import android.os.Parcelable;
import android.os.PersistableBundle;
-import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -141,7 +140,6 @@
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;
@@ -970,7 +968,6 @@
private UiTranslationController mUiTranslationController;
private SplashScreen mSplashScreen;
- private SplashScreenView mSplashScreenView;
private final WindowControllerCallback mWindowControllerCallback =
new WindowControllerCallback() {
@@ -1641,16 +1638,6 @@
}
}
- /** @hide */
- public void setSplashScreenView(SplashScreenView v) {
- mSplashScreenView = v;
- }
-
- /** @hide */
- SplashScreenView getSplashScreenView() {
- return mSplashScreenView;
- }
-
/**
* Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with
* the attribute {@link android.R.attr#persistableMode} set to
@@ -8804,9 +8791,7 @@
* the activity is visible after the screen is turned on when the lockscreen is up. In addition,
* if this flag is set and the activity calls {@link
* KeyguardManager#requestDismissKeyguard(Activity, KeyguardManager.KeyguardDismissCallback)}
- * the screen will turn on. If the screen is off and device is not secured, this flag can turn
- * screen on and dismiss keyguard to make this activity visible and resume, which can be used to
- * replace {@link PowerManager#ACQUIRE_CAUSES_WAKEUP}
+ * the screen will turn on.
*
* @param turnScreenOn {@code true} to turn on the screen; {@code false} otherwise.
*
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d0680f8..431755e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -166,6 +166,7 @@
import android.view.Display;
import android.view.DisplayAdjustments;
import android.view.DisplayAdjustments.FixedRotationAdjustments;
+import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewDebug;
@@ -235,7 +236,6 @@
import java.util.Objects;
import java.util.TimeZone;
import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
@@ -4074,10 +4074,11 @@
@Override
public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
- @Nullable SplashScreenView.SplashScreenViewParcelable parcelable) {
+ @Nullable SplashScreenView.SplashScreenViewParcelable parcelable,
+ @NonNull SurfaceControl startingWindowLeash) {
final DecorView decorView = (DecorView) r.window.peekDecorView();
if (parcelable != null && decorView != null) {
- createSplashScreen(r, decorView, parcelable);
+ createSplashScreen(r, decorView, parcelable, startingWindowLeash);
} else {
// shouldn't happen!
Slog.e(TAG, "handleAttachSplashScreenView failed, unable to attach");
@@ -4085,61 +4086,50 @@
}
private void createSplashScreen(ActivityClientRecord r, DecorView decorView,
- SplashScreenView.SplashScreenViewParcelable parcelable) {
+ SplashScreenView.SplashScreenViewParcelable parcelable,
+ @NonNull SurfaceControl startingWindowLeash) {
final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity);
final SplashScreenView view = builder.createFromParcel(parcelable).build();
decorView.addView(view);
view.attachHostActivityAndSetSystemUIColors(r.activity, r.window);
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);
+
+ view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
+ @Override
+ public void onDraw() {
+ // Transfer the splash screen view from shell to client.
+ // Call syncTransferSplashscreenViewTransaction at the first onDraw so we can ensure
+ // the client view is ready to show and we can use applyTransactionOnDraw to make
+ // all transitions happen at the same frame.
+ syncTransferSplashscreenViewTransaction(
+ view, r.token, decorView, startingWindowLeash);
+ view.postOnAnimation(() -> view.getViewTreeObserver().removeOnDrawListener(this));
+ }
+ });
+ }
+
+ private void reportSplashscreenViewShown(IBinder token, SplashScreenView view) {
+ ActivityClient.getInstance().reportSplashScreenAttached(token);
+ synchronized (this) {
+ if (mSplashScreenGlobal != null) {
+ mSplashScreenGlobal.handOverSplashScreenView(token, view);
+ }
}
}
- @Override
- public void handOverSplashScreenView(@NonNull ActivityClientRecord r) {
- final SplashScreenView v = r.activity.getSplashScreenView();
- if (v == null) {
- return;
- }
- synchronized (this) {
- if (mSplashScreenGlobal != null) {
- mSplashScreenGlobal.handOverSplashScreenView(r.token, v);
- }
- }
+ private void syncTransferSplashscreenViewTransaction(SplashScreenView view, IBinder token,
+ View decorView, @NonNull SurfaceControl startingWindowLeash) {
+ // Ensure splash screen view is shown before remove the splash screen window.
+ // Once the copied splash screen view is onDrawn on decor view, use applyTransactionOnDraw
+ // to ensure the transfer of surface view and hide starting window are happen at the same
+ // frame.
+ final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ transaction.hide(startingWindowLeash);
+
+ decorView.getViewRootImpl().applyTransactionOnDraw(transaction);
+ view.syncTransferSurfaceOnDraw();
+ // Tell server we can remove the starting window
+ decorView.postOnAnimation(() -> reportSplashscreenViewShown(token, view));
}
/**
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 115101c..c743f65 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.view.SurfaceControl;
import android.window.SplashScreenView.SplashScreenViewParcelable;
import com.android.internal.annotations.VisibleForTesting;
@@ -165,10 +166,8 @@
/** 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);
+ @NonNull SplashScreenViewParcelable parcelable,
+ @NonNull SurfaceControl startingWindowLeash);
/** Perform activity launch. */
public abstract Activity handleLaunchActivity(@NonNull ActivityClientRecord r,
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index cac7639..bd9b6e9 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -265,6 +265,13 @@
}
/**
+ * Whether this task is visible.
+ */
+ public boolean isVisible() {
+ return isVisible;
+ }
+
+ /**
* @param isLowResolution
* @return
* @hide
diff --git a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
index 5374984..767fd28 100644
--- a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
+++ b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
@@ -16,17 +16,14 @@
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.view.SurfaceControl;
import android.window.SplashScreenView.SplashScreenViewParcelable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/**
* Transfer a splash screen view to an Activity.
* @hide
@@ -34,31 +31,13 @@
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;
+ private SurfaceControl mStartingWindowLeash;
@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;
- }
+ client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable, mStartingWindowLeash);
}
@Override
@@ -68,26 +47,27 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mRequest);
dest.writeTypedObject(mSplashScreenViewParcelable, flags);
+ dest.writeTypedObject(mStartingWindowLeash, flags);
}
private TransferSplashScreenViewStateItem() {}
private TransferSplashScreenViewStateItem(Parcel in) {
- mRequest = in.readInt();
mSplashScreenViewParcelable = in.readTypedObject(SplashScreenViewParcelable.CREATOR);
+ mStartingWindowLeash = in.readTypedObject(SurfaceControl.CREATOR);
}
/** Obtain an instance initialized with provided params. */
- public static TransferSplashScreenViewStateItem obtain(@TransferRequest int state,
- @Nullable SplashScreenViewParcelable parcelable) {
+ public static TransferSplashScreenViewStateItem obtain(
+ @Nullable SplashScreenViewParcelable parcelable,
+ @Nullable SurfaceControl startingWindowLeash) {
TransferSplashScreenViewStateItem instance =
ObjectPool.obtain(TransferSplashScreenViewStateItem.class);
if (instance == null) {
instance = new TransferSplashScreenViewStateItem();
}
- instance.mRequest = state;
instance.mSplashScreenViewParcelable = parcelable;
+ instance.mStartingWindowLeash = startingWindowLeash;
return instance;
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index ddac22c..9f77a7e 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -22,7 +22,7 @@
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
-import android.hardware.camera2.params.DeviceStateOrientationMap;
+import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
import android.hardware.camera2.params.RecommendedStreamConfigurationMap;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.utils.TypeReference;
@@ -258,11 +258,12 @@
private <T> T overrideProperty(Key<T> key) {
if (CameraCharacteristics.SENSOR_ORIENTATION.equals(key) && (mFoldStateListener != null) &&
(mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS) != null)) {
- DeviceStateOrientationMap deviceStateOrientationMap =
- mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATION_MAP);
+ DeviceStateSensorOrientationMap deviceStateSensorOrientationMap =
+ mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP);
synchronized (mLock) {
- Integer ret = deviceStateOrientationMap.getSensorOrientation(mFoldedDeviceState ?
- DeviceStateOrientationMap.FOLDED : DeviceStateOrientationMap.NORMAL);
+ Integer ret = deviceStateSensorOrientationMap.getSensorOrientation(
+ mFoldedDeviceState ? DeviceStateSensorOrientationMap.FOLDED :
+ DeviceStateSensorOrientationMap.NORMAL);
if (ret >= 0) {
return (T) ret;
} else {
@@ -4056,7 +4057,7 @@
* Clients are advised to not cache or store the orientation value of such logical sensors.
* In case repeated queries to CameraCharacteristics are not preferred, then clients can
* also access the entire mapping from device state to sensor orientation in
- * {@link android.hardware.camera2.params.DeviceStateOrientationMap }.
+ * {@link android.hardware.camera2.params.DeviceStateSensorOrientationMap }.
* Do note that a dynamically changing sensor orientation value in camera characteristics
* will not be the best way to establish the orientation per frame. Clients that want to
* know the sensor orientation of a particular captured frame should query the
@@ -4384,7 +4385,7 @@
* values. The orientation value may need to change depending on the specific folding
* state. Information about the mapping between the device folding state and the
* sensor orientation can be obtained in
- * {@link android.hardware.camera2.params.DeviceStateOrientationMap }.
+ * {@link android.hardware.camera2.params.DeviceStateSensorOrientationMap }.
* Device state orientation maps are optional and maybe present on devices that support
* {@link CaptureRequest#SCALER_ROTATE_AND_CROP android.scaler.rotateAndCrop}.</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
@@ -4398,8 +4399,8 @@
@PublicKey
@NonNull
@SyntheticKey
- public static final Key<android.hardware.camera2.params.DeviceStateOrientationMap> INFO_DEVICE_STATE_ORIENTATION_MAP =
- new Key<android.hardware.camera2.params.DeviceStateOrientationMap>("android.info.deviceStateOrientationMap", android.hardware.camera2.params.DeviceStateOrientationMap.class);
+ public static final Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP =
+ new Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap>("android.info.deviceStateSensorOrientationMap", android.hardware.camera2.params.DeviceStateSensorOrientationMap.class);
/**
* <p>HAL must populate the array with
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 9bb901f..b7c5644 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -30,7 +30,6 @@
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.impl.CameraInjectionSessionImpl;
import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.params.DeviceStateOrientationMap;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfiguration;
@@ -110,8 +109,16 @@
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mFoldStateListener = new FoldStateListener(context);
- context.getSystemService(DeviceStateManager.class)
- .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
+ try {
+ context.getSystemService(DeviceStateManager.class)
+ .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
+ } catch (IllegalStateException e) {
+ Log.v(TAG, "Failed to register device state listener!");
+ Log.v(TAG, "Device state dependent characteristics updates will not be functional!");
+ mHandlerThread.quitSafely();
+ mHandler = null;
+ mFoldStateListener = null;
+ }
}
private HandlerThread mHandlerThread;
@@ -177,7 +184,9 @@
synchronized (mLock) {
DeviceStateListener listener = chars.getDeviceStateListener();
listener.onDeviceStateChanged(mFoldedDeviceState);
- mDeviceStateListeners.add(new WeakReference<>(listener));
+ if (mFoldStateListener != null) {
+ mDeviceStateListeners.add(new WeakReference<>(listener));
+ }
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 3745022..e393a66 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -50,11 +50,10 @@
import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfigurationDuration;
import android.hardware.camera2.marshal.impl.MarshalQueryableString;
import android.hardware.camera2.params.Capability;
-import android.hardware.camera2.params.DeviceStateOrientationMap;
+import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.params.HighSpeedVideoConfiguration;
import android.hardware.camera2.params.LensShadingMap;
-import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.MandatoryStreamCombination;
import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap;
import android.hardware.camera2.params.OisSample;
@@ -763,7 +762,7 @@
}
});
sGetCommandMap.put(
- CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATION_MAP.getNativeKey(),
+ CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP.getNativeKey(),
new GetCommand() {
@Override
@SuppressWarnings("unchecked")
@@ -1004,7 +1003,7 @@
return map;
}
- private DeviceStateOrientationMap getDeviceStateOrientationMap() {
+ private DeviceStateSensorOrientationMap getDeviceStateOrientationMap() {
long[] mapArray = getBase(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS);
// Do not warn if map is null while s is not. This is valid.
@@ -1012,7 +1011,7 @@
return null;
}
- DeviceStateOrientationMap map = new DeviceStateOrientationMap(mapArray);
+ DeviceStateSensorOrientationMap map = new DeviceStateSensorOrientationMap(mapArray);
return map;
}
diff --git a/core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
similarity index 90%
rename from core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java
rename to core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
index 3907f04..200409e 100644
--- a/core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java
+++ b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
@@ -40,7 +40,7 @@
*
* @see CameraCharacteristics#SENSOR_ORIENTATION
*/
-public final class DeviceStateOrientationMap {
+public final class DeviceStateSensorOrientationMap {
/**
* Needs to be kept in sync with the HIDL/AIDL DeviceState
*/
@@ -85,10 +85,10 @@
*
* @hide
*/
- public DeviceStateOrientationMap(final long[] elements) {
+ public DeviceStateSensorOrientationMap(final long[] elements) {
mElements = Objects.requireNonNull(elements, "elements must not be null");
if ((elements.length % 2) != 0) {
- throw new IllegalArgumentException("Device state orientation map length " +
+ throw new IllegalArgumentException("Device state sensor orientation map length " +
elements.length + " is not even!");
}
@@ -121,7 +121,8 @@
}
/**
- * Check if this DeviceStateOrientationMap is equal to another DeviceStateOrientationMap.
+ * Check if this DeviceStateSensorOrientationMap is equal to another
+ * DeviceStateSensorOrientationMap.
*
* <p>Two device state orientation maps are equal if and only if all of their elements are
* {@link Object#equals equal}.</p>
@@ -136,8 +137,8 @@
if (this == obj) {
return true;
}
- if (obj instanceof DeviceStateOrientationMap) {
- final DeviceStateOrientationMap other = (DeviceStateOrientationMap) obj;
+ if (obj instanceof DeviceStateSensorOrientationMap) {
+ final DeviceStateSensorOrientationMap other = (DeviceStateSensorOrientationMap) obj;
return Arrays.equals(mElements, other.mElements);
}
return false;
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index c0f0081..9721279 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -790,11 +790,6 @@
return;
}
- if (Trace.isEnabled()) {
- Binder.enableTracing();
- } else {
- Binder.disableTracing();
- }
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showSoftInput");
ImeTracing.getInstance().triggerServiceDump(
"InputMethodService.InputMethodImpl#showSoftInput", InputMethodService.this,
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 71f90fd2..3d18a89 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -83,11 +83,11 @@
* </intent-filter>
* <meta-data
* android:name="android.service.notification.default_filter_types"
- * android:value="conversations,alerting">
+ * android:value="conversations|alerting">
* </meta-data>
* <meta-data
* android:name="android.service.notification.disabled_filter_types"
- * android:value="ongoing,silent">
+ * android:value="ongoing|silent">
* </meta-data>
* </service></pre>
*
@@ -112,8 +112,9 @@
private final String TAG = getClass().getSimpleName();
/**
- * The name of the {@code meta-data} tag containing a comma separated list of default
- * integer notification types that should be provided to this listener. See
+ * The name of the {@code meta-data} tag containing a pipe separated list of default
+ * integer notification types or "ongoing", "conversations", "alerting", or "silent"
+ * that should be provided to this listener. See
* {@link #FLAG_FILTER_TYPE_ONGOING},
* {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING),
* and {@link #FLAG_FILTER_TYPE_SILENT}.
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index b2fc9a0..69af2a5 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
+import android.hardware.HardwareBuffer;
/**
* Provides an interface to the root-Surface of a View Hierarchy or Window. This
@@ -84,41 +85,43 @@
* Note, when using ANativeWindow APIs in conjunction with a NativeActivity Surface or
* SurfaceView Surface, the buffer producer will already have access to the transform hint and
* no additional work is needed.
+ *
+ * @see HardwareBuffer
*/
- default @Surface.Rotation int getSurfaceTransformHint() {
- return Surface.ROTATION_0;
+ default @SurfaceControl.BufferTransform int getBufferTransformHint() {
+ return SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
}
/**
- * Surface transform hint change listener.
- * @see #getSurfaceTransformHint
+ * Buffer transform hint change listener.
+ * @see #getBufferTransformHint
*/
@UiThread
- interface OnSurfaceTransformHintChangedListener {
+ interface OnBufferTransformHintChangedListener {
/**
* @param hint new surface transform hint
- * @see #getSurfaceTransformHint
+ * @see #getBufferTransformHint
*/
- void onSurfaceTransformHintChanged(@Surface.Rotation int hint);
+ void onBufferTransformHintChanged(@SurfaceControl.BufferTransform int hint);
}
/**
- * Registers a surface transform hint changed listener to receive notifications about when
+ * Registers a {@link OnBufferTransformHintChangedListener} to receive notifications about when
* the transform hint changes.
*
- * @see #getSurfaceTransformHint
- * @see #removeOnSurfaceTransformHintChangedListener
+ * @see #getBufferTransformHint
+ * @see #removeOnBufferTransformHintChangedListener
*/
- default void addOnSurfaceTransformHintChangedListener(
- @NonNull OnSurfaceTransformHintChangedListener listener) {
+ default void addOnBufferTransformHintChangedListener(
+ @NonNull OnBufferTransformHintChangedListener listener) {
}
/**
- * Unregisters a surface transform hint changed listener.
+ * Unregisters a {@link OnBufferTransformHintChangedListener}.
*
- * @see #addOnSurfaceTransformHintChangedListener
+ * @see #addOnBufferTransformHintChangedListener
*/
- default void removeOnSurfaceTransformHintChangedListener(
- @NonNull OnSurfaceTransformHintChangedListener listener) {
+ default void removeOnBufferTransformHintChangedListener(
+ @NonNull OnBufferTransformHintChangedListener listener) {
}
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c786f0f..960d23d 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -241,10 +241,81 @@
private static native void nativeRemoveJankDataListener(long nativeListener);
private static native long nativeCreateJankDataListenerWrapper(OnJankDataListener listener);
private static native int nativeGetGPUContextPriority();
- private static native void nativeSetTransformHint(long nativeObject, int transformHint);
+ private static native void nativeSetTransformHint(long nativeObject,
+ @SurfaceControl.BufferTransform int transformHint);
private static native int nativeGetTransformHint(long nativeObject);
private static native int nativeGetLayerId(long nativeObject);
+ /**
+ * Transforms that can be applied to buffers as they are displayed to a window.
+ *
+ * Supported transforms are any combination of horizontal mirror, vertical mirror, and
+ * clock-wise 90 degree rotation, in that order. Rotations of 180 and 270 degrees are made up
+ * of those basic transforms.
+ * Mirrors {@code ANativeWindowTransform} definitions.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"BUFFER_TRANSFORM_"},
+ value = {BUFFER_TRANSFORM_IDENTITY, BUFFER_TRANSFORM_MIRROR_HORIZONTAL,
+ BUFFER_TRANSFORM_MIRROR_VERTICAL, BUFFER_TRANSFORM_ROTATE_90,
+ BUFFER_TRANSFORM_ROTATE_180, BUFFER_TRANSFORM_ROTATE_270,
+ BUFFER_TRANSFORM_MIRROR_HORIZONTAL | BUFFER_TRANSFORM_ROTATE_90,
+ BUFFER_TRANSFORM_MIRROR_VERTICAL | BUFFER_TRANSFORM_ROTATE_90})
+ public @interface BufferTransform {
+ }
+
+ /**
+ * Identity transform.
+ *
+ * These transforms that can be applied to buffers as they are displayed to a window.
+ * @see HardwareBuffer
+ *
+ * Supported transforms are any combination of horizontal mirror, vertical mirror, and
+ * clock-wise 90 degree rotation, in that order. Rotations of 180 and 270 degrees are
+ * made up of those basic transforms.
+ */
+ public static final int BUFFER_TRANSFORM_IDENTITY = 0x00;
+ /**
+ * Mirror horizontally. Can be combined with {@link #BUFFER_TRANSFORM_MIRROR_VERTICAL}
+ * and {@link #BUFFER_TRANSFORM_ROTATE_90}.
+ */
+ public static final int BUFFER_TRANSFORM_MIRROR_HORIZONTAL = 0x01;
+ /**
+ * Mirror vertically. Can be combined with {@link #BUFFER_TRANSFORM_MIRROR_HORIZONTAL}
+ * and {@link #BUFFER_TRANSFORM_ROTATE_90}.
+ */
+ public static final int BUFFER_TRANSFORM_MIRROR_VERTICAL = 0x02;
+ /**
+ * Rotate 90 degrees clock-wise. Can be combined with {@link
+ * #BUFFER_TRANSFORM_MIRROR_HORIZONTAL} and {@link #BUFFER_TRANSFORM_MIRROR_VERTICAL}.
+ */
+ public static final int BUFFER_TRANSFORM_ROTATE_90 = 0x04;
+ /**
+ * Rotate 180 degrees clock-wise. Cannot be combined with other transforms.
+ */
+ public static final int BUFFER_TRANSFORM_ROTATE_180 =
+ BUFFER_TRANSFORM_MIRROR_HORIZONTAL | BUFFER_TRANSFORM_MIRROR_VERTICAL;
+ /**
+ * Rotate 270 degrees clock-wise. Cannot be combined with other transforms.
+ */
+ public static final int BUFFER_TRANSFORM_ROTATE_270 =
+ BUFFER_TRANSFORM_ROTATE_180 | BUFFER_TRANSFORM_ROTATE_90;
+
+ /**
+ * @hide
+ */
+ public static @BufferTransform int rotationToBufferTransform(@Surface.Rotation int rotation) {
+ switch (rotation) {
+ case Surface.ROTATION_0: return BUFFER_TRANSFORM_IDENTITY;
+ case Surface.ROTATION_90: return BUFFER_TRANSFORM_ROTATE_90;
+ case Surface.ROTATION_180: return BUFFER_TRANSFORM_ROTATE_180;
+ case Surface.ROTATION_270: return BUFFER_TRANSFORM_ROTATE_270;
+ }
+ Log.e(TAG, "Trying to convert unknown rotation=" + rotation);
+ return BUFFER_TRANSFORM_IDENTITY;
+ }
+
@Nullable
@GuardedBy("mLock")
private ArrayList<OnReparentListener> mReparentListeners;
@@ -3659,7 +3730,7 @@
/**
* @hide
*/
- public int getTransformHint() {
+ public @SurfaceControl.BufferTransform int getTransformHint() {
checkNotReleased();
return nativeGetTransformHint(mNativeObject);
}
@@ -3673,7 +3744,7 @@
* with the same size.
* @hide
*/
- public void setTransformHint(@Surface.Rotation int transformHint) {
+ public void setTransformHint(@SurfaceControl.BufferTransform int transformHint) {
nativeSetTransformHint(mNativeObject, transformHint);
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 60bb99d..856dfe5 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -214,7 +214,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
final Rect mSurfaceFrame = new Rect();
int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
- int mTransformHint = 0;
+ @SurfaceControl.BufferTransform int mTransformHint = 0;
private boolean mGlobalListenersAdded;
private boolean mAttachedToWindow;
@@ -1104,7 +1104,7 @@
|| mWindowSpaceTop != mLocation[1];
final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
|| getHeight() != mScreenRect.height();
- final boolean hintChanged = (viewRoot.getSurfaceTransformHint() != mTransformHint)
+ final boolean hintChanged = (viewRoot.getBufferTransformHint() != mTransformHint)
&& mRequestedVisible;
if (creating || formatChanged || sizeChanged || visibleChanged ||
@@ -1130,7 +1130,7 @@
mSurfaceHeight = myHeight;
mFormat = mRequestedFormat;
mLastWindowVisibility = mWindowVisibility;
- mTransformHint = viewRoot.getSurfaceTransformHint();
+ mTransformHint = viewRoot.getBufferTransformHint();
mScreenRect.left = mWindowSpaceLeft;
mScreenRect.top = mWindowSpaceTop;
@@ -1362,7 +1362,7 @@
if (mBlastBufferQueue != null) {
mBlastBufferQueue.destroy();
}
- mTransformHint = viewRoot.getSurfaceTransformHint();
+ mTransformHint = viewRoot.getBufferTransformHint();
mBlastSurfaceControl.setTransformHint(mTransformHint);
mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
mSurfaceHeight, mFormat);
@@ -1889,18 +1889,45 @@
* @param p The SurfacePackage to embed.
*/
public void setChildSurfacePackage(@NonNull SurfaceControlViewHost.SurfacePackage p) {
+ setChildSurfacePackage(p, false /* applyTransactionOnDraw */);
+ }
+
+ /**
+ * Similar to setChildSurfacePackage, but using the BLAST queue so the transaction can be
+ * synchronized with the ViewRootImpl frame.
+ * @hide
+ */
+ public void setChildSurfacePackageOnDraw(
+ @NonNull SurfaceControlViewHost.SurfacePackage p) {
+ setChildSurfacePackage(p, true /* applyTransactionOnDraw */);
+ }
+
+ /**
+ * @param applyTransactionOnDraw Whether to apply transaction at onDraw or immediately.
+ */
+ private void setChildSurfacePackage(
+ @NonNull SurfaceControlViewHost.SurfacePackage p, boolean applyTransactionOnDraw) {
final SurfaceControl lastSc = mSurfacePackage != null ?
mSurfacePackage.getSurfaceControl() : null;
if (mSurfaceControl != null && lastSc != null) {
- mTmpTransaction.reparent(lastSc, null).apply();
+ mTmpTransaction.reparent(lastSc, null);
mSurfacePackage.release();
+ applyTransaction(applyTransactionOnDraw);
} else if (mSurfaceControl != null) {
reparentSurfacePackage(mTmpTransaction, p);
- mTmpTransaction.apply();
+ applyTransaction(applyTransactionOnDraw);
}
mSurfacePackage = p;
}
+ private void applyTransaction(boolean applyTransactionOnDraw) {
+ if (applyTransactionOnDraw) {
+ getViewRootImpl().applyTransactionOnDraw(mTmpTransaction);
+ } else {
+ mTmpTransaction.apply();
+ }
+ }
+
private void reparentSurfacePackage(SurfaceControl.Transaction t,
SurfaceControlViewHost.SurfacePackage p) {
final SurfaceControl sc = p.getSurfaceControl();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b729c9f..572a7cd 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -27060,7 +27060,7 @@
switch (event.mAction) {
case DragEvent.ACTION_DRAG_STARTED: {
- if (result && li.mOnDragListener != null) {
+ if (result && li != null && li.mOnDragListener != null) {
sendWindowContentChangedAccessibilityEvent(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
@@ -27074,7 +27074,8 @@
refreshDrawableState();
} break;
case DragEvent.ACTION_DROP: {
- if (result && (li.mOnDragListener != null | li.mOnReceiveContentListener != null)) {
+ if (result && li != null && (li.mOnDragListener != null
+ || li.mOnReceiveContentListener != null)) {
sendWindowContentChangedAccessibilityEvent(
AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_DROPPED);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c45b27a..5a3a9d5 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -312,9 +312,10 @@
static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<>();
static boolean sFirstDrawComplete = false;
- private ArrayList<OnSurfaceTransformHintChangedListener> mTransformHintListeners =
+ private ArrayList<OnBufferTransformHintChangedListener> mTransformHintListeners =
new ArrayList<>();
- private @Surface.Rotation int mPreviousTransformHint = Surface.ROTATION_0;
+ private @SurfaceControl.BufferTransform
+ int mPreviousTransformHint = SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
/**
* Callback for notifying about global configuration changes.
*/
@@ -10499,38 +10500,38 @@
}
@Override
- public @Surface.Rotation int getSurfaceTransformHint() {
+ public @SurfaceControl.BufferTransform int getBufferTransformHint() {
return mSurfaceControl.getTransformHint();
}
@Override
- public void addOnSurfaceTransformHintChangedListener(
- OnSurfaceTransformHintChangedListener listener) {
+ public void addOnBufferTransformHintChangedListener(
+ OnBufferTransformHintChangedListener listener) {
Objects.requireNonNull(listener);
if (mTransformHintListeners.contains(listener)) {
throw new IllegalArgumentException(
- "attempt to call addOnSurfaceTransformHintChangedListener() "
+ "attempt to call addOnBufferTransformHintChangedListener() "
+ "with a previously registered listener");
}
mTransformHintListeners.add(listener);
}
@Override
- public void removeOnSurfaceTransformHintChangedListener(
- OnSurfaceTransformHintChangedListener listener) {
+ public void removeOnBufferTransformHintChangedListener(
+ OnBufferTransformHintChangedListener listener) {
Objects.requireNonNull(listener);
mTransformHintListeners.remove(listener);
}
- private void dispatchTransformHintChanged(@Surface.Rotation int hint) {
+ private void dispatchTransformHintChanged(@SurfaceControl.BufferTransform int hint) {
if (mTransformHintListeners.isEmpty()) {
return;
}
- ArrayList<OnSurfaceTransformHintChangedListener> listeners =
- (ArrayList<OnSurfaceTransformHintChangedListener>) mTransformHintListeners.clone();
+ ArrayList<OnBufferTransformHintChangedListener> listeners =
+ (ArrayList<OnBufferTransformHintChangedListener>) mTransformHintListeners.clone();
for (int i = 0; i < listeners.size(); i++) {
- OnSurfaceTransformHintChangedListener listener = listeners.get(i);
- listener.onSurfaceTransformHintChanged(hint);
+ OnBufferTransformHintChangedListener listener = listeners.get(i);
+ listener.onBufferTransformHintChanged(hint);
}
}
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 7631269..1c915cb 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -352,25 +352,11 @@
throw e.rethrowFromSystemServer();
}
- int size = possibleDisplayInfos.size();
- DisplayInfo currentDisplayInfo;
- WindowInsets windowInsets = null;
- if (size > 0) {
- currentDisplayInfo = possibleDisplayInfos.get(0);
-
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0;
- // TODO(181127261) not computing insets correctly - need to have underlying
- // frame reflect the faked orientation.
- windowInsets = getWindowInsetsFromServerForDisplay(
- currentDisplayInfo.displayId, params,
- new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
- currentDisplayInfo.getNaturalHeight()), isScreenRound,
- WINDOWING_MODE_FULLSCREEN);
- }
-
Set<WindowMetrics> maxMetrics = new HashSet<>();
- for (int i = 0; i < size; i++) {
+ WindowInsets windowInsets;
+ DisplayInfo currentDisplayInfo;
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ for (int i = 0; i < possibleDisplayInfos.size(); i++) {
currentDisplayInfo = possibleDisplayInfos.get(i);
// Calculate max bounds for this rotation and state.
@@ -378,7 +364,18 @@
currentDisplayInfo.logicalHeight);
// Calculate insets for the rotated max bounds.
- // TODO(181127261) calculate insets for each display rotation and state.
+ final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0;
+ // Initialize insets based upon display rotation. Note any window-provided insets
+ // will not be set.
+ windowInsets = getWindowInsetsFromServerForDisplay(
+ currentDisplayInfo.displayId, params,
+ new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
+ currentDisplayInfo.getNaturalHeight()), isScreenRound,
+ WINDOWING_MODE_FULLSCREEN);
+ // Set the hardware-provided insets.
+ windowInsets = new WindowInsets.Builder(windowInsets).setRoundedCorners(
+ currentDisplayInfo.roundedCorners)
+ .setDisplayCutout(currentDisplayInfo.displayCutout).build();
maxMetrics.add(new WindowMetrics(maxBounds, windowInsets));
}
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 60402eb..aa73ed7 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -426,15 +426,19 @@
continue;
}
mActivity.runOnUiThread(() -> {
+ ViewTranslationCallback callback = view.getViewTranslationCallback();
if (view.getViewTranslationResponse() != null
&& view.getViewTranslationResponse().equals(response)) {
- if (DEBUG) {
- Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
- + ". Ignoring.");
+ if (callback instanceof TextViewTranslationCallback) {
+ if (((TextViewTranslationCallback) callback).isShowingTranslation()) {
+ if (DEBUG) {
+ Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
+ + ". Ignoring.");
+ }
+ return;
+ }
}
- return;
}
- ViewTranslationCallback callback = view.getViewTranslationCallback();
if (callback == null) {
if (view instanceof TextView) {
// developer doesn't provide their override, we set the default TextView
diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java
index 152405b..4a78f3e 100644
--- a/core/java/android/widget/TextViewTranslationCallback.java
+++ b/core/java/android/widget/TextViewTranslationCallback.java
@@ -64,6 +64,12 @@
*/
@Override
public boolean onShowTranslation(@NonNull View view) {
+ if (mIsShowingTranslation) {
+ if (DEBUG) {
+ Log.d(TAG, view + " is already showing translated text.");
+ }
+ return false;
+ }
ViewTranslationResponse response = view.getViewTranslationResponse();
if (response == null) {
Log.e(TAG, "onShowTranslation() shouldn't be called before "
@@ -152,7 +158,7 @@
return true;
}
- boolean isShowingTranslation() {
+ public boolean isShowingTranslation() {
return mIsShowingTranslation;
}
diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java
index 3e00758..3354a6c 100644
--- a/core/java/android/window/SplashScreen.java
+++ b/core/java/android/window/SplashScreen.java
@@ -241,7 +241,6 @@
public void handOverSplashScreenView(@NonNull IBinder token,
@NonNull SplashScreenView splashScreenView) {
- transferSurface(splashScreenView);
dispatchOnExitAnimation(token, splashScreenView);
}
@@ -265,9 +264,5 @@
return impl != null && impl.mExitAnimationListener != null;
}
}
-
- private void transferSurface(@NonNull SplashScreenView splashScreenView) {
- splashScreenView.transferSurface();
- }
}
}
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index f14294e..f748d4b 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -464,7 +464,10 @@
}
- void transferSurface() {
+ /**
+ * @hide
+ */
+ public void syncTransferSurfaceOnDraw() {
if (mSurfacePackage == null) {
return;
}
@@ -474,8 +477,8 @@
String.format("SurfacePackage'surface reparented to %s", parent)));
Log.d(TAG, "Transferring surface " + mSurfaceView.toString());
}
- mSurfaceView.setChildSurfacePackage(mSurfacePackage);
+ mSurfaceView.setChildSurfacePackageOnDraw(mSurfacePackage);
}
void initIconAnimation(Drawable iconDrawable, long duration) {
@@ -533,10 +536,6 @@
restoreSystemUIColors();
mWindow = null;
}
- if (mHostActivity != null) {
- mHostActivity.setSplashScreenView(null);
- mHostActivity = null;
- }
mHasRemoved = true;
}
@@ -582,7 +581,6 @@
* @hide
*/
public void attachHostActivityAndSetSystemUIColors(Activity activity, Window window) {
- activity.setSplashScreenView(this);
mHostActivity = activity;
mWindow = window;
final WindowManager.LayoutParams attr = window.getAttributes();
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 9d6488d..bbf8138 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -295,6 +295,36 @@
}
/**
+ * Reparent's all children tasks or the top task of {@param currentParent} in the specified
+ * {@param windowingMode} and {@param activityType} to {@param newParent} in their current
+ * z-order.
+ *
+ * @param currentParent of the tasks to perform the operation no.
+ * {@code null} will perform the operation on the display.
+ * @param newParent for the tasks. {@code null} will perform the operation on the display.
+ * @param windowingModes of the tasks to reparent.
+ * @param activityTypes of the tasks to reparent.
+ * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
+ * the bottom.
+ * @param reparentTopOnly When {@code true}, only reparent the top task which fit windowingModes
+ * and activityTypes.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction reparentTasks(@Nullable WindowContainerToken currentParent,
+ @Nullable WindowContainerToken newParent, @Nullable int[] windowingModes,
+ @Nullable int[] activityTypes, boolean onTop, boolean reparentTopOnly) {
+ mHierarchyOps.add(HierarchyOp.createForChildrenTasksReparent(
+ currentParent != null ? currentParent.asBinder() : null,
+ newParent != null ? newParent.asBinder() : null,
+ windowingModes,
+ activityTypes,
+ onTop,
+ reparentTopOnly));
+ return this;
+ }
+
+ /**
* Reparent's all children tasks of {@param currentParent} in the specified
* {@param windowingMode} and {@param activityType} to {@param newParent} in their current
* z-order.
@@ -311,13 +341,8 @@
public WindowContainerTransaction reparentTasks(@Nullable WindowContainerToken currentParent,
@Nullable WindowContainerToken newParent, @Nullable int[] windowingModes,
@Nullable int[] activityTypes, boolean onTop) {
- mHierarchyOps.add(HierarchyOp.createForChildrenTasksReparent(
- currentParent != null ? currentParent.asBinder() : null,
- newParent != null ? newParent.asBinder() : null,
- windowingModes,
- activityTypes,
- onTop));
- return this;
+ return reparentTasks(currentParent, newParent, windowingModes, activityTypes, onTop,
+ false /* reparentTopOnly */);
}
/**
@@ -948,6 +973,8 @@
// Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
private boolean mToTop;
+ private boolean mReparentTopOnly;
+
@Nullable
private int[] mWindowingModes;
@@ -985,13 +1012,15 @@
}
public static HierarchyOp createForChildrenTasksReparent(IBinder currentParent,
- IBinder newParent, int[] windowingModes, int[] activityTypes, boolean onTop) {
+ IBinder newParent, int[] windowingModes, int[] activityTypes, boolean onTop,
+ boolean reparentTopOnly) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT)
.setContainer(currentParent)
.setReparentContainer(newParent)
.setWindowingModes(windowingModes)
.setActivityTypes(activityTypes)
.setToTop(onTop)
+ .setReparentTopOnly(reparentTopOnly)
.build();
}
@@ -1040,6 +1069,7 @@
mContainer = copy.mContainer;
mReparent = copy.mReparent;
mToTop = copy.mToTop;
+ mReparentTopOnly = copy.mReparentTopOnly;
mWindowingModes = copy.mWindowingModes;
mActivityTypes = copy.mActivityTypes;
mLaunchOptions = copy.mLaunchOptions;
@@ -1053,6 +1083,7 @@
mContainer = in.readStrongBinder();
mReparent = in.readStrongBinder();
mToTop = in.readBoolean();
+ mReparentTopOnly = in.readBoolean();
mWindowingModes = in.createIntArray();
mActivityTypes = in.createIntArray();
mLaunchOptions = in.readBundle();
@@ -1093,6 +1124,10 @@
return mToTop;
}
+ public boolean getReparentTopOnly() {
+ return mReparentTopOnly;
+ }
+
public int[] getWindowingModes() {
return mWindowingModes;
}
@@ -1126,12 +1161,13 @@
switch (mType) {
case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
return "{ChildrenTasksReparent: from=" + mContainer + " to=" + mReparent
- + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes
- + " mActivityType=" + mActivityTypes + "}";
+ + " mToTop=" + mToTop + " mReparentTopOnly=" + mReparentTopOnly
+ + " mWindowingMode=" + Arrays.toString(mWindowingModes)
+ + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT:
return "{SetLaunchRoot: container=" + mContainer
- + " mWindowingMode=" + mWindowingModes
- + " mActivityType=" + mActivityTypes + "}";
+ + " mWindowingMode=" + Arrays.toString(mWindowingModes)
+ + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
case HIERARCHY_OP_TYPE_REPARENT:
return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ")
+ mReparent + "}";
@@ -1163,8 +1199,9 @@
+ " adjacentContainer=" + mReparent + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
- + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes
- + " mActivityType=" + mActivityTypes + "}";
+ + " mToTop=" + mToTop
+ + " mWindowingMode=" + Arrays.toString(mWindowingModes)
+ + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
}
}
@@ -1174,6 +1211,7 @@
dest.writeStrongBinder(mContainer);
dest.writeStrongBinder(mReparent);
dest.writeBoolean(mToTop);
+ dest.writeBoolean(mReparentTopOnly);
dest.writeIntArray(mWindowingModes);
dest.writeIntArray(mActivityTypes);
dest.writeBundle(mLaunchOptions);
@@ -1211,6 +1249,8 @@
private boolean mToTop;
+ private boolean mReparentTopOnly;
+
@Nullable
private int[] mWindowingModes;
@@ -1248,6 +1288,11 @@
return this;
}
+ Builder setReparentTopOnly(boolean reparentTopOnly) {
+ mReparentTopOnly = reparentTopOnly;
+ return this;
+ }
+
Builder setWindowingModes(@Nullable int[] windowingModes) {
mWindowingModes = windowingModes;
return this;
@@ -1290,6 +1335,7 @@
? Arrays.copyOf(mActivityTypes, mActivityTypes.length)
: null;
hierarchyOp.mToTop = mToTop;
+ hierarchyOp.mReparentTopOnly = mReparentTopOnly;
hierarchyOp.mLaunchOptions = mLaunchOptions;
hierarchyOp.mActivityIntent = mActivityIntent;
hierarchyOp.mPendingIntent = mPendingIntent;
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index db019a67..954204f 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -84,6 +84,7 @@
Consts.TAG_WM),
WM_DEBUG_LAYER_MIRRORING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM),
+ WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 65ff7c7..1452c67 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1815,16 +1815,12 @@
if (surface == nullptr) {
return;
}
- surface->setTransformHint(
- ui::Transform::toRotationFlags(static_cast<ui::Rotation>(transformHint)));
+ surface->setTransformHint(transformHint);
}
static jint nativeGetTransformHint(JNIEnv* env, jclass clazz, jlong nativeSurfaceControl) {
sp<SurfaceControl> surface(reinterpret_cast<SurfaceControl*>(nativeSurfaceControl));
- ui::Transform::RotationFlags transformHintRotationFlags =
- static_cast<ui::Transform::RotationFlags>(surface->getTransformHint());
-
- return toRotationInt(ui::Transform::toRotation((transformHintRotationFlags)));
+ return surface->getTransformHint();
}
static jint nativeGetLayerId(JNIEnv* env, jclass clazz, jlong nativeSurfaceControl) {
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 4af9d75..6f81b82 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -69,6 +69,7 @@
// know what activity types to check for when invoking splitscreen multi-window.
optional bool is_home_recents_component = 6;
repeated IdentifierProto pending_activities = 7 [deprecated=true];
+ optional int32 default_min_size_resizable_task = 8;
}
message BarControllerProto {
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 67b2ee4..a974e90 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -336,7 +336,7 @@
<string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Legt die Zoom-Stufe und -Position auf dem Display fest."</string>
<string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Touch-Gesten möglich"</string>
<string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Tippen, Wischen, Zusammenziehen und andere Touch-Gesten möglich."</string>
- <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gesten auf dem Fingerabdrucksensor"</string>
+ <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gesten auf dem Fingerabdrucksensor"</string>
<string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Erfasst Touch-Gesten auf dem Fingerabdrucksensor des Geräts."</string>
<string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Screenshot erstellen"</string>
<string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Es kann ein Screenshot des Displays erstellt werden."</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 4bb06bb..eaed8c8 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -100,7 +100,7 @@
<string name="peerTtyModeHco" msgid="5626377160840915617">"समवयस्क व्यक्तीने TTY मोड HCO ची विनंती केली"</string>
<string name="peerTtyModeVco" msgid="572208600818270944">"समवयस्क व्यक्तीने TTY मोड VCO ची विनंती केली"</string>
<string name="peerTtyModeOff" msgid="2420380956369226583">"समवयस्क व्यक्तीने TTY मोड बंद ची विनंती केली"</string>
- <string name="serviceClassVoice" msgid="2065556932043454987">"Voice"</string>
+ <string name="serviceClassVoice" msgid="2065556932043454987">"व्हॉइस"</string>
<string name="serviceClassData" msgid="4148080018967300248">"डेटा"</string>
<string name="serviceClassFAX" msgid="2561653371698904118">"फॅक्स"</string>
<string name="serviceClassSMS" msgid="1547664561704509004">"SMS"</string>
@@ -306,7 +306,7 @@
<string name="permgroupdesc_contacts" msgid="9163927941244182567">"आपल्या संपर्कांवर प्रवेश"</string>
<string name="permgrouplab_location" msgid="1858277002233964394">"स्थान"</string>
<string name="permgroupdesc_location" msgid="1995955142118450685">"या डिव्हाइसच्या स्थानावर प्रवेश"</string>
- <string name="permgrouplab_calendar" msgid="6426860926123033230">"Calendar"</string>
+ <string name="permgrouplab_calendar" msgid="6426860926123033230">"कॅलेंडर"</string>
<string name="permgroupdesc_calendar" msgid="6762751063361489379">"आपल्या कॅलेंडरवर प्रवेश"</string>
<string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
<string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS मेसेज पाठवणे आणि पाहणे हे"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ddc9f02..3718d28 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4068,7 +4068,7 @@
<string translatable="false" name="config_inCallNotificationSound">/product/media/audio/ui/InCallNotification.ogg</string>
<!-- URI for default ringtone sound file to be used for silent ringer vibration -->
- <string translatable="false" name="config_defaultRingtoneVibrationSound">/product/media/audio/ui/AttentionalHaptics.ogg</string>
+ <string translatable="false" name="config_defaultRingtoneVibrationSound"></string>
<!-- Default number of notifications from the same app before they are automatically grouped by the OS -->
<integer translatable="false" name="config_autoGroupAtCount">4</integer>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 909ca39..9573607 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -595,6 +595,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-1478175541": {
+ "message": "No longer animating wallpaper targets!",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-1474602871": {
"message": "Launch on display check: disallow launch on virtual display for not-embedded activity.",
"level": "DEBUG",
@@ -1621,6 +1627,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-360208282": {
+ "message": "Animating wallpapers: old: %s hidden=%b new: %s hidden=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-354571697": {
"message": "Existence Changed in transition %d: %s",
"level": "VERBOSE",
@@ -1675,6 +1687,12 @@
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-304728471": {
+ "message": "New wallpaper: target=%s prev=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-302468788": {
"message": "Expected target rootTask=%s to be top most but found rootTask=%s",
"level": "WARN",
@@ -1693,6 +1711,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-275077723": {
+ "message": "New animation: %s old animation: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-262984451": {
"message": "Relaunch failed %s",
"level": "INFO",
@@ -1747,6 +1771,12 @@
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-182877285": {
+ "message": "Wallpaper layer changed: assigning layers + relayout",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-177040661": {
"message": "Start rotation animation. customAnim=%s, mCurRotation=%s, mOriginalRotation=%s",
"level": "DEBUG",
@@ -2005,6 +2035,12 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "114070759": {
+ "message": "New wallpaper target: %s prevTarget: %s caller=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"115358443": {
"message": "Focus changing: %s -> %s",
"level": "INFO",
@@ -2347,6 +2383,12 @@
"group": "WM_DEBUG_RESIZE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "422634333": {
+ "message": "First draw done in potential wallpaper target %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"424524729": {
"message": "Attempted to add wallpaper window with unknown token %s. Aborting.",
"level": "WARN",
@@ -2371,12 +2413,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "457951957": {
- "message": "\tNot visible=%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_REMOTE_ANIMATIONS",
- "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
- },
"463993897": {
"message": "Aborted waiting for drawn: %s",
"level": "WARN",
@@ -2425,6 +2461,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowContainerThumbnail.java"
},
+ "535103992": {
+ "message": "Wallpaper may change! Adjusting",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"539077569": {
"message": "Clear freezing of %s force=%b",
"level": "VERBOSE",
@@ -2653,6 +2695,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "733466617": {
+ "message": "Wallpaper token %s visible=%b",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperWindowToken.java"
+ },
"736692676": {
"message": "Config is relaunching %s",
"level": "VERBOSE",
@@ -2989,6 +3037,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "1178653181": {
+ "message": "Old wallpaper still the target.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"1186730970": {
"message": " no common mode yet, so set it",
"level": "VERBOSE",
@@ -3667,6 +3721,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowAnimator.java"
},
+ "1984843251": {
+ "message": "Hiding wallpaper %s from %s target=%s prev=%s callers=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"1995093920": {
"message": "Checking to restart %s: changed=0x%s, handles=0x%s, mLastReportedConfiguration=%s",
"level": "VERBOSE",
@@ -3697,6 +3757,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "2024493888": {
+ "message": "\tWallpaper of display=%s is not visible",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+ "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
+ },
"2028163120": {
"message": "applyAnimation: anim=%s nextAppTransition=ANIM_SCALE_UP transit=%s isEntrance=%s Callers=%s",
"level": "VERBOSE",
@@ -3721,12 +3787,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
- "2057434754": {
- "message": "\tvisible=%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_REMOTE_ANIMATIONS",
- "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
- },
"2060978050": {
"message": "moveWindowTokenToDisplay: Attempted to move token: %s to non-exiting displayId=%d",
"level": "WARN",
@@ -3867,6 +3927,9 @@
"WM_DEBUG_TASKS": {
"tag": "WindowManager"
},
+ "WM_DEBUG_WALLPAPER": {
+ "tag": "WindowManager"
+ },
"WM_DEBUG_WINDOW_INSETS": {
"tag": "WindowManager"
},
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
index 06f6228..194b633 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
@@ -16,7 +16,8 @@
package androidx.window.extensions.embedding;
-import android.graphics.Point;
+import static android.graphics.Matrix.MSCALE_X;
+
import android.graphics.Rect;
import android.view.Choreographer;
import android.view.RemoteAnimationTarget;
@@ -25,58 +26,151 @@
import android.view.animation.Transformation;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
/**
* Wrapper to handle the TaskFragment animation update in one {@link SurfaceControl.Transaction}.
+ *
+ * The base adapter can be used for {@link RemoteAnimationTarget} that is simple open/close.
*/
class TaskFragmentAnimationAdapter {
- private final Animation mAnimation;
- private final RemoteAnimationTarget mTarget;
- private final SurfaceControl mLeash;
- private final boolean mSizeChanged;
- private final Point mPosition;
- private final Transformation mTransformation = new Transformation();
- private final float[] mMatrix = new float[9];
- private final float[] mVecs = new float[4];
- private final Rect mRect = new Rect();
+ final Animation mAnimation;
+ final RemoteAnimationTarget mTarget;
+ final SurfaceControl mLeash;
+
+ final Transformation mTransformation = new Transformation();
+ final float[] mMatrix = new float[9];
private boolean mIsFirstFrame = true;
TaskFragmentAnimationAdapter(@NonNull Animation animation,
@NonNull RemoteAnimationTarget target) {
- this(animation, target, target.leash, false /* sizeChanged */, null /* position */);
+ this(animation, target, target.leash);
}
/**
- * @param sizeChanged whether the surface size needs to be changed.
+ * @param leash the surface to animate.
*/
TaskFragmentAnimationAdapter(@NonNull Animation animation,
- @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash,
- boolean sizeChanged, @Nullable Point position) {
+ @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash) {
mAnimation = animation;
mTarget = target;
mLeash = leash;
- mSizeChanged = sizeChanged;
- mPosition = position != null
- ? position
- : new Point(target.localBounds.left, target.localBounds.top);
}
/** Called on frame update. */
- void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
+ final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
if (mIsFirstFrame) {
t.show(mLeash);
mIsFirstFrame = false;
}
- currentPlayTime = Math.min(currentPlayTime, mAnimation.getDuration());
- mAnimation.getTransformation(currentPlayTime, mTransformation);
- mTransformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
+ // Extract the transformation to the current time.
+ mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()),
+ mTransformation);
+ t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+ onAnimationUpdateInner(t);
+ }
+
+ /** To be overridden by subclasses to adjust the animation surface change. */
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ mTransformation.getMatrix().postTranslate(
+ mTarget.localBounds.left, mTarget.localBounds.top);
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
t.setAlpha(mLeash, mTransformation.getAlpha());
- t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+ }
- if (mSizeChanged) {
+ /** Called after animation finished. */
+ final void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
+ onAnimationUpdate(t, mAnimation.getDuration());
+ }
+
+ final long getDurationHint() {
+ return mAnimation.computeDurationHint();
+ }
+
+ /**
+ * Should be used when the {@link RemoteAnimationTarget} is in split with others, and want to
+ * animate together as one. This adapter will offset the animation leash to make the animate of
+ * two windows look like a single window.
+ */
+ static class SplitAdapter extends TaskFragmentAnimationAdapter {
+ private final boolean mIsLeftHalf;
+ private final int mWholeAnimationWidth;
+
+ /**
+ * @param isLeftHalf whether this is the left half of the animation.
+ * @param wholeAnimationWidth the whole animation windows width.
+ */
+ SplitAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target,
+ boolean isLeftHalf, int wholeAnimationWidth) {
+ super(animation, target);
+ mIsLeftHalf = isLeftHalf;
+ mWholeAnimationWidth = wholeAnimationWidth;
+ if (wholeAnimationWidth == 0) {
+ throw new IllegalArgumentException("SplitAdapter must provide wholeAnimationWidth");
+ }
+ }
+
+ @Override
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ float posX = mTarget.localBounds.left;
+ final float posY = mTarget.localBounds.top;
+ // This window is half of the whole animation window. Offset left/right to make it
+ // look as one with the other half.
+ mTransformation.getMatrix().getValues(mMatrix);
+ final int targetWidth = mTarget.localBounds.width();
+ final float scaleX = mMatrix[MSCALE_X];
+ final float totalOffset = mWholeAnimationWidth * (1 - scaleX) / 2;
+ final float curOffset = targetWidth * (1 - scaleX) / 2;
+ final float offsetDiff = totalOffset - curOffset;
+ if (mIsLeftHalf) {
+ posX += offsetDiff;
+ } else {
+ posX -= offsetDiff;
+ }
+ mTransformation.getMatrix().postTranslate(posX, posY);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+ }
+ }
+
+ /**
+ * Should be used for the animation of the snapshot of a {@link RemoteAnimationTarget} that has
+ * size change.
+ */
+ static class SnapshotAdapter extends TaskFragmentAnimationAdapter {
+
+ SnapshotAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
+ // Start leash is the snapshot of the starting surface.
+ super(animation, target, target.startLeash);
+ }
+
+ @Override
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ // Snapshot should always be placed at the top left of the animation leash.
+ mTransformation.getMatrix().postTranslate(0, 0);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+ }
+ }
+
+ /**
+ * Should be used for the animation of the {@link RemoteAnimationTarget} that has size change.
+ */
+ static class BoundsChangeAdapter extends TaskFragmentAnimationAdapter {
+ private final float[] mVecs = new float[4];
+ private final Rect mRect = new Rect();
+
+ BoundsChangeAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
+ super(animation, target);
+ }
+
+ @Override
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ mTransformation.getMatrix().postTranslate(
+ mTarget.localBounds.left, mTarget.localBounds.top);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+
// The following applies an inverse scale to the clip-rect so that it crops "after" the
// scale instead of before.
mVecs[1] = mVecs[2] = 0;
@@ -92,13 +186,4 @@
t.setWindowCrop(mLeash, mRect);
}
}
-
- /** Called after animation finished. */
- void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
- onAnimationUpdate(t, mAnimation.getDuration());
- }
-
- long getDurationHint() {
- return mAnimation.computeDurationHint();
- }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
index 6579766..535dac1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -29,7 +29,6 @@
class TaskFragmentAnimationController {
private static final String TAG = "TaskFragAnimationCtrl";
- // TODO(b/196173550) turn off when finalize
static final boolean DEBUG = false;
private final TaskFragmentOrganizer mOrganizer;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index 3980d07..412559e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -23,7 +23,7 @@
import android.animation.Animator;
import android.animation.ValueAnimator;
-import android.graphics.Point;
+import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -40,6 +40,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.function.BiFunction;
/** To run the TaskFragment animations. */
class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
@@ -167,42 +168,86 @@
private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
- final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
- for (RemoteAnimationTarget target : targets) {
- final Animation animation =
- mAnimationSpec.loadOpenAnimation(target.mode != MODE_CLOSING /* isEnter */);
- adapters.add(new TaskFragmentAnimationAdapter(animation, target));
- }
- return adapters;
+ return createOpenCloseAnimationAdapters(targets,
+ mAnimationSpec::loadOpenAnimation);
}
private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
- final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
+ return createOpenCloseAnimationAdapters(targets,
+ mAnimationSpec::loadCloseAnimation);
+ }
+
+ private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters(
+ @NonNull RemoteAnimationTarget[] targets,
+ @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) {
+ // We need to know if the target window is only a partial of the whole animation screen.
+ // If so, we will need to adjust it to make the whole animation screen looks like one.
+ final List<RemoteAnimationTarget> openingTargets = new ArrayList<>();
+ final List<RemoteAnimationTarget> closingTargets = new ArrayList<>();
+ final Rect openingWholeScreenBounds = new Rect();
+ final Rect closingWholeScreenBounds = new Rect();
for (RemoteAnimationTarget target : targets) {
- final Animation animation =
- mAnimationSpec.loadCloseAnimation(target.mode != MODE_CLOSING /* isEnter */);
- adapters.add(new TaskFragmentAnimationAdapter(animation, target));
+ if (target.mode != MODE_CLOSING) {
+ openingTargets.add(target);
+ openingWholeScreenBounds.union(target.localBounds);
+ } else {
+ closingTargets.add(target);
+ closingWholeScreenBounds.union(target.localBounds);
+ }
+ }
+
+ final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
+ for (RemoteAnimationTarget target : openingTargets) {
+ adapters.add(createOpenCloseAnimationAdapter(target, animationProvider,
+ openingWholeScreenBounds));
+ }
+ for (RemoteAnimationTarget target : closingTargets) {
+ adapters.add(createOpenCloseAnimationAdapter(target, animationProvider,
+ closingWholeScreenBounds));
}
return adapters;
}
+ private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter(
+ @NonNull RemoteAnimationTarget target,
+ @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider,
+ @NonNull Rect wholeAnimationBounds) {
+ final Animation animation = animationProvider.apply(target, wholeAnimationBounds);
+ final Rect targetBounds = target.localBounds;
+ if (targetBounds.left == wholeAnimationBounds.left
+ && targetBounds.right != wholeAnimationBounds.right) {
+ // This is the left split of the whole animation window.
+ return new TaskFragmentAnimationAdapter.SplitAdapter(animation, target,
+ true /* isLeftHalf */, wholeAnimationBounds.width());
+ } else if (targetBounds.left != wholeAnimationBounds.left
+ && targetBounds.right == wholeAnimationBounds.right) {
+ // This is the right split of the whole animation window.
+ return new TaskFragmentAnimationAdapter.SplitAdapter(animation, target,
+ false /* isLeftHalf */, wholeAnimationBounds.width());
+ }
+ // Open/close window that fills the whole animation.
+ return new TaskFragmentAnimationAdapter(animation, target);
+ }
+
private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
for (RemoteAnimationTarget target : targets) {
if (target.startBounds != null) {
+ // This is the target with bounds change.
final Animation[] animations =
mAnimationSpec.createChangeBoundsChangeAnimations(target);
- // The snapshot surface will always be at (0, 0) of its parent.
- adapters.add(new TaskFragmentAnimationAdapter(animations[0], target,
- target.startLeash, false /* sizeChanged */, new Point(0, 0)));
- // The end surface will have size change for scaling.
- adapters.add(new TaskFragmentAnimationAdapter(animations[1], target,
- target.leash, true /* sizeChanged */, null /* position */));
+ // Adapter for the starting snapshot leash.
+ adapters.add(new TaskFragmentAnimationAdapter.SnapshotAdapter(
+ animations[0], target));
+ // Adapter for the ending bounds changed leash.
+ adapters.add(new TaskFragmentAnimationAdapter.BoundsChangeAdapter(
+ animations[1], target));
continue;
}
+ // These are the other targets that don't have bounds change in the same transition.
final Animation animation;
if (target.hasAnimatingParent) {
// No-op if it will be covered by the changing parent window.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index 11a79b2..c0908a5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -176,18 +176,28 @@
return new Animation[]{startSet, endSet};
}
- Animation loadOpenAnimation(boolean isEnter) {
- // TODO(b/196173550) We need to customize the animation to handle two open window as one.
- return mTransitionAnimation.loadDefaultAnimationAttr(isEnter
+ Animation loadOpenAnimation(@NonNull RemoteAnimationTarget target,
+ @NonNull Rect wholeAnimationBounds) {
+ final boolean isEnter = target.mode != MODE_CLOSING;
+ final Animation animation = mTransitionAnimation.loadDefaultAnimationAttr(isEnter
? R.styleable.WindowAnimation_activityOpenEnterAnimation
: R.styleable.WindowAnimation_activityOpenExitAnimation);
+ animation.initialize(target.localBounds.width(), target.localBounds.height(),
+ wholeAnimationBounds.width(), wholeAnimationBounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
}
- Animation loadCloseAnimation(boolean isEnter) {
- // TODO(b/196173550) We need to customize the animation to handle two open window as one.
- return mTransitionAnimation.loadDefaultAnimationAttr(isEnter
+ Animation loadCloseAnimation(@NonNull RemoteAnimationTarget target,
+ @NonNull Rect wholeAnimationBounds) {
+ final boolean isEnter = target.mode != MODE_CLOSING;
+ final Animation animation = mTransitionAnimation.loadDefaultAnimationAttr(isEnter
? R.styleable.WindowAnimation_activityCloseEnterAnimation
: R.styleable.WindowAnimation_activityCloseExitAnimation);
+ animation.initialize(target.localBounds.width(), target.localBounds.height(),
+ wholeAnimationBounds.width(), wholeAnimationBounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
}
private class SettingsObserver extends ContentObserver {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
index e6f8388..224db02 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
@@ -36,6 +36,6 @@
* @return API version string in MAJOR.MINOR.PATCH-description format.
*/
public static String getApiVersion() {
- return "0.1.0-settings_sample";
+ return "1.0.0-reference";
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index 7f82ebd..a47a152 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -49,25 +49,24 @@
return mIsActive;
}
- void activate(Rect rootBounds, WindowContainerTransaction wct) {
+ void activate(Rect rootBounds, WindowContainerTransaction wct, boolean includingTopTask) {
if (mIsActive) return;
final WindowContainerToken rootToken = mRootTaskInfo.token;
wct.setBounds(rootToken, rootBounds)
.setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW)
- .setLaunchRoot(
- rootToken,
- CONTROLLED_WINDOWING_MODES,
- CONTROLLED_ACTIVITY_TYPES)
- .reparentTasks(
- null /* currentParent */,
- rootToken,
- CONTROLLED_WINDOWING_MODES,
- CONTROLLED_ACTIVITY_TYPES,
- true /* onTop */)
// Moving the root task to top after the child tasks were re-parented , or the root
// task cannot be visible and focused.
.reorder(rootToken, true /* onTop */);
+ if (includingTopTask) {
+ wct.reparentTasks(
+ null /* currentParent */,
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */,
+ true /* reparentTopOnly */);
+ }
mIsActive = true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 414b4e4..3e74ad3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -263,7 +263,7 @@
@SplitPosition int sideStagePosition) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
setSideStagePosition(sideStagePosition, wct);
- mMainStage.activate(getMainStageBounds(), wct);
+ mMainStage.activate(getMainStageBounds(), wct, true /* reparent */);
mSideStage.addTask(task, getSideStageBounds(), wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> updateSurfaceBounds(null /* layout */, t));
@@ -299,7 +299,7 @@
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct);
+ mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
mSideStage.setBounds(getSideStageBounds(), wct);
// Make sure the launch options will put tasks in the corresponding split roots
@@ -368,7 +368,7 @@
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct);
+ mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
mSideStage.setBounds(getSideStageBounds(), wct);
// Make sure the launch options will put tasks in the corresponding split roots
@@ -756,7 +756,7 @@
} else if (isSideStage) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
// Make sure the main stage is active.
- mMainStage.activate(getMainStageBounds(), wct);
+ mMainStage.activate(getMainStageBounds(), wct, true /* reparent */);
mSideStage.setBounds(getSideStageBounds(), wct);
mTaskOrganizer.applyTransaction(wct);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
new file mode 100644
index 0000000..45f6d3c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
@@ -0,0 +1,103 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.window.RemoteTransition;
+
+import com.android.wm.shell.stagesplit.ISplitScreenListener;
+
+/**
+ * Interface that is exposed to remote callers to manipulate the splitscreen feature.
+ */
+interface ISplitScreen {
+
+ /**
+ * Registers a split screen listener.
+ */
+ oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1;
+
+ /**
+ * Unregisters a split screen listener.
+ */
+ oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
+
+ /**
+ * Hides the side-stage if it is currently visible.
+ */
+ oneway void setSideStageVisibility(boolean visible) = 3;
+
+ /**
+ * Removes a task from the side stage.
+ */
+ oneway void removeFromSideStage(int taskId) = 4;
+
+ /**
+ * Removes the split-screen stages and leaving indicated task to top. Passing INVALID_TASK_ID
+ * to indicate leaving no top task after leaving split-screen.
+ */
+ oneway void exitSplitScreen(int toTopTaskId) = 5;
+
+ /**
+ * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible.
+ */
+ oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6;
+
+ /**
+ * Starts a task in a stage.
+ */
+ oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7;
+
+ /**
+ * Starts a shortcut in a stage.
+ */
+ oneway void startShortcut(String packageName, String shortcutId, int stage, int position,
+ in Bundle options, in UserHandle user) = 8;
+
+ /**
+ * Starts an activity in a stage.
+ */
+ oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage,
+ int position, in Bundle options) = 9;
+
+ /**
+ * Starts tasks simultaneously in one transition.
+ */
+ oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
+ in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10;
+
+ /**
+ * Version of startTasks using legacy transition system.
+ */
+ oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
+ int sideTaskId, in Bundle sideOptions, int sidePosition,
+ in RemoteAnimationAdapter adapter) = 11;
+
+ /**
+ * Blocking call that notifies and gets additional split-screen targets when entering
+ * recents (for example: the dividerBar).
+ * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled).
+ * @param appTargets apps that will be re-parented to display area
+ */
+ RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ in RemoteAnimationTarget[] appTargets) = 12;
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl
new file mode 100644
index 0000000..46e4299
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
+ */
+oneway interface ISplitScreenListener {
+
+ /**
+ * Called when the stage position changes.
+ */
+ void onStagePositionChanged(int stage, int position);
+
+ /**
+ * Called when a task changes stages.
+ */
+ void onTaskStageChanged(int taskId, int stage, boolean visible);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
new file mode 100644
index 0000000..83855be
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
@@ -0,0 +1,104 @@
+/*
+ * 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 com.android.wm.shell.stagesplit;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Main stage for split-screen mode. When split-screen is active all standard activity types launch
+ * on the main stage, except for task that are explicitly pinned to the {@link SideStage}.
+ * @see StageCoordinator
+ */
+class MainStage extends StageTaskListener {
+ private static final String TAG = MainStage.class.getSimpleName();
+
+ private boolean mIsActive = false;
+
+ MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+ stageTaskUnfoldController);
+ }
+
+ boolean isActive() {
+ return mIsActive;
+ }
+
+ void activate(Rect rootBounds, WindowContainerTransaction wct) {
+ if (mIsActive) return;
+
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setBounds(rootToken, rootBounds)
+ .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW)
+ .setLaunchRoot(
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES)
+ .reparentTasks(
+ null /* currentParent */,
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */)
+ // Moving the root task to top after the child tasks were re-parented , or the root
+ // task cannot be visible and focused.
+ .reorder(rootToken, true /* onTop */);
+
+ mIsActive = true;
+ }
+
+ void deactivate(WindowContainerTransaction wct) {
+ deactivate(wct, false /* toTop */);
+ }
+
+ void deactivate(WindowContainerTransaction wct, boolean toTop) {
+ if (!mIsActive) return;
+ mIsActive = false;
+
+ if (mRootTaskInfo == null) return;
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setLaunchRoot(
+ rootToken,
+ null,
+ null)
+ .reparentTasks(
+ rootToken,
+ null /* newParent */,
+ CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
+ CONTROLLED_ACTIVITY_TYPES,
+ toTop)
+ // We want this re-order to the bottom regardless since we are re-parenting
+ // all its tasks.
+ .reorder(rootToken, false /* onTop */);
+ }
+
+ void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) {
+ wct.setBounds(mRootTaskInfo.token, bounds)
+ .setWindowingMode(mRootTaskInfo.token, windowingMode);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
new file mode 100644
index 0000000..8fbad52
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
@@ -0,0 +1,181 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.view.IWindow;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+import android.widget.FrameLayout;
+
+import com.android.wm.shell.R;
+
+/**
+ * Handles drawing outline of the bounds of provided root surface. The outline will be drown with
+ * the consideration of display insets like status bar, navigation bar and display cutout.
+ */
+class OutlineManager extends WindowlessWindowManager {
+ private static final String WINDOW_NAME = "SplitOutlineLayer";
+ private final Context mContext;
+ private final Rect mRootBounds = new Rect();
+ private final Rect mTempRect = new Rect();
+ private final Rect mLastOutlineBounds = new Rect();
+ private final InsetsState mInsetsState = new InsetsState();
+ private final int mExpandedTaskBarHeight;
+ private OutlineView mOutlineView;
+ private SurfaceControlViewHost mViewHost;
+ private SurfaceControl mHostLeash;
+ private SurfaceControl mLeash;
+
+ OutlineManager(Context context, Configuration configuration) {
+ super(configuration, null /* rootSurface */, null /* hostInputToken */);
+ mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
+ null /* options */);
+ mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.taskbar_frame_height);
+ }
+
+ @Override
+ protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ b.setParent(mHostLeash);
+ }
+
+ void inflate(SurfaceControl rootLeash, Rect rootBounds) {
+ if (mLeash != null || mViewHost != null) return;
+
+ mHostLeash = rootLeash;
+ mRootBounds.set(rootBounds);
+ mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+
+ final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext)
+ .inflate(R.layout.split_outline, null);
+ mOutlineView = rootLayout.findViewById(R.id.split_outline);
+
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
+ FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
+ lp.width = mRootBounds.width();
+ lp.height = mRootBounds.height();
+ lp.token = new Binder();
+ lp.setTitle(WINDOW_NAME);
+ lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
+ // TRUSTED_OVERLAY for windowless window without input channel.
+ mViewHost.setView(rootLayout, lp);
+ mLeash = getSurfaceControl(mViewHost.getWindowToken());
+
+ drawOutline();
+ }
+
+ void release() {
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+ mRootBounds.setEmpty();
+ mLastOutlineBounds.setEmpty();
+ mOutlineView = null;
+ mHostLeash = null;
+ mLeash = null;
+ }
+
+ @Nullable
+ SurfaceControl getOutlineLeash() {
+ return mLeash;
+ }
+
+ void setVisibility(boolean visible) {
+ if (mOutlineView != null) {
+ mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ void setRootBounds(Rect rootBounds) {
+ if (mViewHost == null || mViewHost.getView() == null) {
+ return;
+ }
+
+ if (!mRootBounds.equals(rootBounds)) {
+ WindowManager.LayoutParams lp =
+ (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
+ lp.width = rootBounds.width();
+ lp.height = rootBounds.height();
+ mViewHost.relayout(lp);
+ mRootBounds.set(rootBounds);
+ drawOutline();
+ }
+ }
+
+ void onInsetsChanged(InsetsState insetsState) {
+ if (!mInsetsState.equals(insetsState)) {
+ mInsetsState.set(insetsState);
+ drawOutline();
+ }
+ }
+
+ private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) {
+ outBounds.set(rootBounds);
+ final InsetsSource taskBarInsetsSource =
+ insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ // Only insets the divider bar with task bar when it's expanded so that the rounded corners
+ // will be drawn against task bar.
+ if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds));
+ }
+
+ // Offset the coordinate from screen based to surface based.
+ outBounds.offset(-rootBounds.left, -rootBounds.top);
+ }
+
+ void drawOutline() {
+ if (mOutlineView == null) {
+ return;
+ }
+
+ computeOutlineBounds(mRootBounds, mInsetsState, mTempRect);
+ if (mTempRect.equals(mLastOutlineBounds)) {
+ return;
+ }
+
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams();
+ lp.leftMargin = mTempRect.left;
+ lp.topMargin = mTempRect.top;
+ lp.width = mTempRect.width();
+ lp.height = mTempRect.height();
+ mOutlineView.setLayoutParams(lp);
+ mLastOutlineBounds.set(mTempRect);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
new file mode 100644
index 0000000..92b1381
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
@@ -0,0 +1,82 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
+import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.RoundedCorner;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.R;
+
+/** View for drawing split outline. */
+public class OutlineView extends View {
+ private final Paint mPaint = new Paint();
+ private final Path mPath = new Path();
+ private final float[] mRadii = new float[8];
+
+ public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(
+ getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
+ mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null));
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ // TODO(b/200850654): match the screen corners with the actual display decor.
+ mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT);
+ mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT);
+ mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT);
+ mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT);
+ }
+
+ private int getCornerRadius(@RoundedCorner.Position int position) {
+ final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position);
+ return roundedCorner == null ? 0 : roundedCorner.getRadius();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (changed) {
+ mPath.reset();
+ mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawPath(mPath, mPaint);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
new file mode 100644
index 0000000..55c4f3a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
@@ -0,0 +1,144 @@
+/*
+ * 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 com.android.wm.shell.stagesplit;
+
+import android.annotation.CallSuper;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
+ * here. All other task are launch in the {@link MainStage}.
+ *
+ * @see StageCoordinator
+ */
+class SideStage extends StageTaskListener implements
+ DisplayInsetsController.OnInsetsChangedListener {
+ private static final String TAG = SideStage.class.getSimpleName();
+ private final Context mContext;
+ private OutlineManager mOutlineManager;
+
+ SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+ stageTaskUnfoldController);
+ mContext = context;
+ }
+
+ void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
+ WindowContainerTransaction wct) {
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setBounds(rootToken, rootBounds)
+ .reparent(task.token, rootToken, true /* onTop*/)
+ // Moving the root task to top after the child tasks were reparented , or the root
+ // task cannot be visible and focused.
+ .reorder(rootToken, true /* onTop */);
+ }
+
+ boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
+ // No matter if the root task is empty or not, moving the root to bottom because it no
+ // longer preserves visible child task.
+ wct.reorder(mRootTaskInfo.token, false /* onTop */);
+ if (mChildrenTaskInfo.size() == 0) return false;
+ wct.reparentTasks(
+ mRootTaskInfo.token,
+ null /* newParent */,
+ CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
+ CONTROLLED_ACTIVITY_TYPES,
+ toTop);
+ return true;
+ }
+
+ boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
+ final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
+ if (task == null) return false;
+ wct.reparent(task.token, newParent, false /* onTop */);
+ return true;
+ }
+
+ @Nullable
+ public SurfaceControl getOutlineLeash() {
+ return mOutlineManager.getOutlineLeash();
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ super.onTaskAppeared(taskInfo, leash);
+ if (isRootTask(taskInfo)) {
+ mOutlineManager = new OutlineManager(mContext, taskInfo.configuration);
+ enableOutline(true);
+ }
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ super.onTaskInfoChanged(taskInfo);
+ if (isRootTask(taskInfo)) {
+ mOutlineManager.setRootBounds(taskInfo.configuration.windowConfiguration.getBounds());
+ }
+ }
+
+ private boolean isRootTask(ActivityManager.RunningTaskInfo taskInfo) {
+ return mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId;
+ }
+
+ void enableOutline(boolean enable) {
+ if (mOutlineManager == null) {
+ return;
+ }
+
+ if (enable) {
+ if (mRootTaskInfo != null) {
+ mOutlineManager.inflate(mRootLeash,
+ mRootTaskInfo.configuration.windowConfiguration.getBounds());
+ }
+ } else {
+ mOutlineManager.release();
+ }
+ }
+
+ void setOutlineVisibility(boolean visible) {
+ mOutlineManager.setVisibility(visible);
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ mOutlineManager.onInsetsChanged(insetsState);
+ }
+
+ @Override
+ public void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] activeControls) {
+ insetsChanged(insetsState);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
new file mode 100644
index 0000000..aec81a1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
@@ -0,0 +1,99 @@
+/*
+ * 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 com.android.wm.shell.stagesplit;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface to engage split-screen feature.
+ * TODO: Figure out which of these are actually needed outside of the Shell
+ */
+@ExternalThread
+public interface SplitScreen {
+ /**
+ * Stage type isn't specified normally meaning to use what ever the default is.
+ * E.g. exit split-screen and launch the app in fullscreen.
+ */
+ int STAGE_TYPE_UNDEFINED = -1;
+ /**
+ * The main stage type.
+ * @see MainStage
+ */
+ int STAGE_TYPE_MAIN = 0;
+
+ /**
+ * The side stage type.
+ * @see SideStage
+ */
+ int STAGE_TYPE_SIDE = 1;
+
+ @IntDef(prefix = { "STAGE_TYPE_" }, value = {
+ STAGE_TYPE_UNDEFINED,
+ STAGE_TYPE_MAIN,
+ STAGE_TYPE_SIDE
+ })
+ @interface StageType {}
+
+ /** Callback interface for listening to changes in a split-screen stage. */
+ interface SplitScreenListener {
+ default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
+ default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
+ default void onSplitVisibilityChanged(boolean visible) {}
+ }
+
+ /** Registers listener that gets split screen callback. */
+ void registerSplitScreenListener(@NonNull SplitScreenListener listener,
+ @NonNull Executor executor);
+
+ /** Unregisters listener that gets split screen callback. */
+ void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
+
+ /**
+ * Returns a binder that can be passed to an external process to manipulate SplitScreen.
+ */
+ default ISplitScreen createExternalInterface() {
+ return null;
+ }
+
+ /**
+ * Called when the keyguard occluded state changes.
+ * @param occluded Indicates if the keyguard is now occluded.
+ */
+ void onKeyguardOccludedChanged(boolean occluded);
+
+ /**
+ * Called when the visibility of the keyguard changes.
+ * @param showing Indicates if the keyguard is now visible.
+ */
+ void onKeyguardVisibilityChanged(boolean showing);
+
+ /** Get a string representation of a stage type */
+ static String stageTypeToString(@StageType int stage) {
+ switch (stage) {
+ case STAGE_TYPE_UNDEFINED: return "UNDEFINED";
+ case STAGE_TYPE_MAIN: return "MAIN";
+ case STAGE_TYPE_SIDE: return "SIDE";
+ default: return "UNKNOWN(" + stage + ")";
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
new file mode 100644
index 0000000..94db9cd9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
@@ -0,0 +1,595 @@
+/*
+ * 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 com.android.wm.shell.stagesplit;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
+import android.window.RemoteTransition;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+import com.android.wm.shell.draganddrop.DragAndDropPolicy;
+import com.android.wm.shell.transition.LegacyTransitions;
+import com.android.wm.shell.transition.Transitions;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
+import javax.inject.Provider;
+
+/**
+ * Class manages split-screen multitasking mode and implements the main interface
+ * {@link SplitScreen}.
+ * @see StageCoordinator
+ */
+// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
+public class SplitScreenController implements DragAndDropPolicy.Starter,
+ RemoteCallable<SplitScreenController> {
+ private static final String TAG = SplitScreenController.class.getSimpleName();
+
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final SyncTransactionQueue mSyncQueue;
+ private final Context mContext;
+ private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ private final ShellExecutor mMainExecutor;
+ private final SplitScreenImpl mImpl = new SplitScreenImpl();
+ private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final Transitions mTransitions;
+ private final TransactionPool mTransactionPool;
+ private final SplitscreenEventLogger mLogger;
+ private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
+
+ private StageCoordinator mStageCoordinator;
+
+ public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue, Context context,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ ShellExecutor mainExecutor, DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ Transitions transitions, TransactionPool transactionPool,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mTaskOrganizer = shellTaskOrganizer;
+ mSyncQueue = syncQueue;
+ mContext = context;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mMainExecutor = mainExecutor;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mTransitions = transitions;
+ mTransactionPool = transactionPool;
+ mUnfoldControllerProvider = unfoldControllerProvider;
+ mLogger = new SplitscreenEventLogger();
+ }
+
+ public SplitScreen asSplitScreen() {
+ return mImpl;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ public void onOrganizerRegistered() {
+ if (mStageCoordinator == null) {
+ // TODO: Multi-display
+ mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
+ mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
+ mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
+ mUnfoldControllerProvider);
+ }
+ }
+
+ public boolean isSplitScreenVisible() {
+ return mStageCoordinator.isSplitScreenVisible();
+ }
+
+ public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
+ final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
+ if (task == null) {
+ throw new IllegalArgumentException("Unknown taskId" + taskId);
+ }
+ return moveToSideStage(task, sideStagePosition);
+ }
+
+ public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+ @SplitPosition int sideStagePosition) {
+ return mStageCoordinator.moveToSideStage(task, sideStagePosition);
+ }
+
+ public boolean removeFromSideStage(int taskId) {
+ return mStageCoordinator.removeFromSideStage(taskId);
+ }
+
+ public void setSideStageOutline(boolean enable) {
+ mStageCoordinator.setSideStageOutline(enable);
+ }
+
+ public void setSideStagePosition(@SplitPosition int sideStagePosition) {
+ mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
+ }
+
+ public void setSideStageVisibility(boolean visible) {
+ mStageCoordinator.setSideStageVisibility(visible);
+ }
+
+ public void enterSplitScreen(int taskId, boolean leftOrTop) {
+ moveToSideStage(taskId,
+ leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ }
+
+ public void exitSplitScreen(int toTopTaskId, int exitReason) {
+ mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+ }
+
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ mStageCoordinator.onKeyguardOccludedChanged(occluded);
+ }
+
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mStageCoordinator.onKeyguardVisibilityChanged(showing);
+ }
+
+ public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ }
+
+ public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
+ mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
+ }
+
+ public void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mStageCoordinator.registerSplitScreenListener(listener);
+ }
+
+ public void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mStageCoordinator.unregisterSplitScreenListener(listener);
+ }
+
+ public void startTask(int taskId, @SplitScreen.StageType int stage,
+ @SplitPosition int position, @Nullable Bundle options) {
+ options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
+
+ try {
+ ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to launch task", e);
+ }
+ }
+
+ public void startShortcut(String packageName, String shortcutId,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options, UserHandle user) {
+ options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
+
+ try {
+ LauncherApps launcherApps =
+ mContext.getSystemService(LauncherApps.class);
+ launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
+ options, user);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "Failed to launch shortcut", e);
+ }
+ }
+
+ public void startIntent(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options) {
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ startIntentLegacy(intent, fillInIntent, stage, position, options);
+ return;
+ }
+ mStageCoordinator.startIntent(intent, fillInIntent, stage, position, options,
+ null /* remote */);
+ }
+
+ private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options) {
+ LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback,
+ SurfaceControl.Transaction t) {
+ mStageCoordinator.updateSurfaceBounds(null /* layout */, t);
+
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
+ }
+
+ t.apply();
+ if (finishedCallback != null) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error finishing legacy transition: ", e);
+ }
+ }
+ }
+ };
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = mStageCoordinator.resolveStartStage(stage, position, options, wct);
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+ }
+
+ RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) {
+ if (!isSplitScreenVisible()) return null;
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setContainerLayer()
+ .setName("RecentsAnimationSplitTasks")
+ .setHidden(false)
+ .setCallsite("SplitScreenController#onGoingtoRecentsLegacy");
+ mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
+ SurfaceControl sc = builder.build();
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+
+ // Ensure that we order these in the parent in the right z-order as their previous order
+ Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex);
+ int layer = 1;
+ for (RemoteAnimationTarget appTarget : apps) {
+ transaction.reparent(appTarget.leash, sc);
+ transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
+ appTarget.screenSpaceBounds.top);
+ transaction.setLayer(appTarget.leash, layer++);
+ }
+ transaction.apply();
+ transaction.close();
+ return new RemoteAnimationTarget[]{
+ mStageCoordinator.getDividerBarLegacyTarget(),
+ mStageCoordinator.getOutlineLegacyTarget()};
+ }
+
+ /**
+ * Sets drag info to be logged when splitscreen is entered.
+ */
+ public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ mStageCoordinator.logOnDroppedToSplit(position, dragSessionId);
+ }
+
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + TAG);
+ if (mStageCoordinator != null) {
+ mStageCoordinator.dump(pw, prefix);
+ }
+ }
+
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
+ @ExternalThread
+ private class SplitScreenImpl implements SplitScreen {
+ private ISplitScreenImpl mISplitScreen;
+ private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
+ private final SplitScreenListener mListener = new SplitScreenListener() {
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onStagePositionChanged(stage, position);
+ });
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible);
+ });
+ }
+ }
+
+ @Override
+ public void onSplitVisibilityChanged(boolean visible) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onSplitVisibilityChanged(visible);
+ });
+ }
+ }
+ };
+
+ @Override
+ public ISplitScreen createExternalInterface() {
+ if (mISplitScreen != null) {
+ mISplitScreen.invalidate();
+ }
+ mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
+ return mISplitScreen;
+ }
+
+ @Override
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ mMainExecutor.execute(() -> {
+ SplitScreenController.this.onKeyguardOccludedChanged(occluded);
+ });
+ }
+
+ @Override
+ public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
+ if (mExecutors.containsKey(listener)) return;
+
+ mMainExecutor.execute(() -> {
+ if (mExecutors.size() == 0) {
+ SplitScreenController.this.registerSplitScreenListener(mListener);
+ }
+
+ mExecutors.put(listener, executor);
+ });
+
+ executor.execute(() -> {
+ mStageCoordinator.sendStatusToListener(listener);
+ });
+ }
+
+ @Override
+ public void unregisterSplitScreenListener(SplitScreenListener listener) {
+ mMainExecutor.execute(() -> {
+ mExecutors.remove(listener);
+
+ if (mExecutors.size() == 0) {
+ SplitScreenController.this.unregisterSplitScreenListener(mListener);
+ }
+ });
+ }
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mMainExecutor.execute(() -> {
+ SplitScreenController.this.onKeyguardVisibilityChanged(showing);
+ });
+ }
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class ISplitScreenImpl extends ISplitScreen.Stub {
+ private SplitScreenController mController;
+ private ISplitScreenListener mListener;
+ private final SplitScreen.SplitScreenListener mSplitScreenListener =
+ new SplitScreen.SplitScreenListener() {
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ try {
+ if (mListener != null) {
+ mListener.onStagePositionChanged(stage, position);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onStagePositionChanged", e);
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ try {
+ if (mListener != null) {
+ mListener.onTaskStageChanged(taskId, stage, visible);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onTaskStageChanged", e);
+ }
+ }
+ };
+ private final IBinder.DeathRecipient mListenerDeathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ @BinderThread
+ public void binderDied() {
+ final SplitScreenController controller = mController;
+ controller.getRemoteCallExecutor().execute(() -> {
+ mListener = null;
+ controller.unregisterSplitScreenListener(mSplitScreenListener);
+ });
+ }
+ };
+
+ public ISplitScreenImpl(SplitScreenController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public void registerSplitScreenListener(ISplitScreenListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
+ (controller) -> {
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ if (listener != null) {
+ try {
+ listener.asBinder().linkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death");
+ return;
+ }
+ }
+ mListener = listener;
+ controller.registerSplitScreenListener(mSplitScreenListener);
+ });
+ }
+
+ @Override
+ public void unregisterSplitScreenListener(ISplitScreenListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
+ (controller) -> {
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ mListener = null;
+ controller.unregisterSplitScreenListener(mSplitScreenListener);
+ });
+ }
+
+ @Override
+ public void exitSplitScreen(int toTopTaskId) {
+ executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
+ (controller) -> {
+ controller.exitSplitScreen(toTopTaskId,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT);
+ });
+ }
+
+ @Override
+ public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
+ (controller) -> {
+ controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ });
+ }
+
+ @Override
+ public void setSideStageVisibility(boolean visible) {
+ executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility",
+ (controller) -> {
+ controller.setSideStageVisibility(visible);
+ });
+ }
+
+ @Override
+ public void removeFromSideStage(int taskId) {
+ executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
+ (controller) -> {
+ controller.removeFromSideStage(taskId);
+ });
+ }
+
+ @Override
+ public void startTask(int taskId, int stage, int position, @Nullable Bundle options) {
+ executeRemoteCallWithTaskPermission(mController, "startTask",
+ (controller) -> {
+ controller.startTask(taskId, stage, position, options);
+ });
+ }
+
+ @Override
+ public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ RemoteAnimationAdapter adapter) {
+ executeRemoteCallWithTaskPermission(mController, "startTasks",
+ (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
+ mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
+ adapter));
+ }
+
+ @Override
+ public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions,
+ @SplitPosition int sidePosition,
+ @Nullable RemoteTransition remoteTransition) {
+ executeRemoteCallWithTaskPermission(mController, "startTasks",
+ (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
+ sideTaskId, sideOptions, sidePosition, remoteTransition));
+ }
+
+ @Override
+ public void startShortcut(String packageName, String shortcutId, int stage, int position,
+ @Nullable Bundle options, UserHandle user) {
+ executeRemoteCallWithTaskPermission(mController, "startShortcut",
+ (controller) -> {
+ controller.startShortcut(packageName, shortcutId, stage, position,
+ options, user);
+ });
+ }
+
+ @Override
+ public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
+ @Nullable Bundle options) {
+ executeRemoteCallWithTaskPermission(mController, "startIntent",
+ (controller) -> {
+ controller.startIntent(intent, fillInIntent, stage, position, options);
+ });
+ }
+
+ @Override
+ public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ RemoteAnimationTarget[] apps) {
+ final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null};
+ executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy",
+ (controller) -> out[0] = controller.onGoingToRecentsLegacy(cancel, apps),
+ true /* blocking */);
+ return out[0];
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
new file mode 100644
index 0000000..af9a5aa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
@@ -0,0 +1,298 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
+
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
+import static com.android.wm.shell.transition.Transitions.isOpeningType;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.RemoteTransition;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.transition.OneShotRemoteHandler;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+
+/** Manages transition animations for split-screen. */
+class SplitScreenTransitions {
+ private static final String TAG = "SplitScreenTransitions";
+
+ /** Flag applied to a transition change to identify it as a divider bar for animation. */
+ public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
+
+ private final TransactionPool mTransactionPool;
+ private final Transitions mTransitions;
+ private final Runnable mOnFinish;
+
+ IBinder mPendingDismiss = null;
+ IBinder mPendingEnter = null;
+
+ private IBinder mAnimatingTransition = null;
+ private OneShotRemoteHandler mRemoteHandler = null;
+
+ private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> {
+ if (wct != null || wctCB != null) {
+ throw new UnsupportedOperationException("finish transactions not supported yet.");
+ }
+ onFinish();
+ };
+
+ /** Keeps track of currently running animations */
+ private final ArrayList<Animator> mAnimations = new ArrayList<>();
+
+ private Transitions.TransitionFinishCallback mFinishCallback = null;
+ private SurfaceControl.Transaction mFinishTransaction;
+
+ SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
+ @NonNull Runnable onFinishCallback) {
+ mTransactionPool = pool;
+ mTransitions = transitions;
+ mOnFinish = onFinishCallback;
+ }
+
+ void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
+ mFinishCallback = finishCallback;
+ mAnimatingTransition = transition;
+ if (mRemoteHandler != null) {
+ mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction,
+ mRemoteFinishCB);
+ mRemoteHandler = null;
+ return;
+ }
+ playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
+ }
+
+ private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
+ @NonNull WindowContainerToken sideRoot) {
+ mFinishTransaction = mTransactionPool.acquire();
+
+ // Play some place-holder fade animations
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final SurfaceControl leash = change.getLeash();
+ final int mode = info.getChanges().get(i).getMode();
+
+ if (mode == TRANSIT_CHANGE) {
+ if (change.getParent() != null) {
+ // This is probably reparented, so we want the parent to be immediately visible
+ final TransitionInfo.Change parentChange = info.getChange(change.getParent());
+ t.show(parentChange.getLeash());
+ t.setAlpha(parentChange.getLeash(), 1.f);
+ // and then animate this layer outside the parent (since, for example, this is
+ // the home task animating from fullscreen to part-screen).
+ t.reparent(leash, info.getRootLeash());
+ t.setLayer(leash, info.getChanges().size() - i);
+ // build the finish reparent/reposition
+ mFinishTransaction.reparent(leash, parentChange.getLeash());
+ mFinishTransaction.setPosition(leash,
+ change.getEndRelOffset().x, change.getEndRelOffset().y);
+ }
+ // TODO(shell-transitions): screenshot here
+ final Rect startBounds = new Rect(change.getStartAbsBounds());
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Dismissing split via snap which means the still-visible task has been
+ // dragged to its end position at animation start so reflect that here.
+ startBounds.offsetTo(change.getEndAbsBounds().left,
+ change.getEndAbsBounds().top);
+ }
+ final Rect endBounds = new Rect(change.getEndAbsBounds());
+ startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+ endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+ startExampleResizeAnimation(leash, startBounds, endBounds);
+ }
+ if (change.getParent() != null) {
+ continue;
+ }
+
+ if (transition == mPendingEnter && (mainRoot.equals(change.getContainer())
+ || sideRoot.equals(change.getContainer()))) {
+ t.setWindowCrop(leash, change.getStartAbsBounds().width(),
+ change.getStartAbsBounds().height());
+ }
+ boolean isOpening = isOpeningType(info.getType());
+ if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
+ // fade in
+ startExampleAnimation(leash, true /* show */);
+ } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
+ // fade out
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Dismissing via snap-to-top/bottom means that the dismissed task is already
+ // not-visible (usually cropped to oblivion) so immediately set its alpha to 0
+ // and don't animate it so it doesn't pop-in when reparented.
+ t.setAlpha(leash, 0.f);
+ } else {
+ startExampleAnimation(leash, false /* show */);
+ }
+ }
+ }
+ t.apply();
+ onFinish();
+ }
+
+ /** Starts a transition to enter split with a remote transition animator. */
+ IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
+ @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
+ @NonNull Transitions.TransitionHandler handler) {
+ if (remoteTransition != null) {
+ // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
+ mRemoteHandler = new OneShotRemoteHandler(
+ mTransitions.getMainExecutor(), remoteTransition);
+ }
+ final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
+ mPendingEnter = transition;
+ if (mRemoteHandler != null) {
+ mRemoteHandler.setTransition(transition);
+ }
+ return transition;
+ }
+
+ /** Starts a transition for dismissing split after dragging the divider to a screen edge */
+ IBinder startSnapToDismiss(@NonNull WindowContainerTransaction wct,
+ @NonNull Transitions.TransitionHandler handler) {
+ final IBinder transition = mTransitions.startTransition(
+ TRANSIT_SPLIT_DISMISS_SNAP, wct, handler);
+ mPendingDismiss = transition;
+ return transition;
+ }
+
+ void onFinish() {
+ if (!mAnimations.isEmpty()) return;
+ mOnFinish.run();
+ if (mFinishTransaction != null) {
+ mFinishTransaction.apply();
+ mTransactionPool.release(mFinishTransaction);
+ mFinishTransaction = null;
+ }
+ mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ mFinishCallback = null;
+ if (mAnimatingTransition == mPendingEnter) {
+ mPendingEnter = null;
+ }
+ if (mAnimatingTransition == mPendingDismiss) {
+ mPendingDismiss = null;
+ }
+ mAnimatingTransition = null;
+ }
+
+ // TODO(shell-transitions): real animations
+ private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
+ final float end = show ? 1.f : 0.f;
+ final float start = 1.f - end;
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final ValueAnimator va = ValueAnimator.ofFloat(start, end);
+ va.setDuration(500);
+ va.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
+ transaction.apply();
+ });
+ final Runnable finisher = () -> {
+ transaction.setAlpha(leash, end);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ mTransitions.getMainExecutor().execute(() -> {
+ mAnimations.remove(va);
+ onFinish();
+ });
+ };
+ va.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) { }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) { }
+ });
+ mAnimations.add(va);
+ mTransitions.getAnimExecutor().execute(va::start);
+ }
+
+ // TODO(shell-transitions): real animations
+ private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
+ @NonNull Rect startBounds, @NonNull Rect endBounds) {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
+ va.setDuration(500);
+ va.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ transaction.setWindowCrop(leash,
+ (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
+ (int) (startBounds.height() * (1.f - fraction)
+ + endBounds.height() * fraction));
+ transaction.setPosition(leash,
+ startBounds.left * (1.f - fraction) + endBounds.left * fraction,
+ startBounds.top * (1.f - fraction) + endBounds.top * fraction);
+ transaction.apply();
+ });
+ final Runnable finisher = () -> {
+ transaction.setWindowCrop(leash, 0, 0);
+ transaction.setPosition(leash, endBounds.left, endBounds.top);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ mTransitions.getMainExecutor().execute(() -> {
+ mAnimations.remove(va);
+ onFinish();
+ });
+ };
+ va.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ finisher.run();
+ }
+ });
+ mAnimations.add(va);
+ mTransitions.getAnimExecutor().execute(va::start);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
new file mode 100644
index 0000000..aab7902
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
@@ -0,0 +1,324 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+
+/**
+ * Helper class that to log Drag & Drop UIEvents for a single session, see also go/uievent
+ */
+public class SplitscreenEventLogger {
+
+ // Used to generate instance ids for this drag if one is not provided
+ private final InstanceIdSequence mIdSequence;
+
+ // The instance id for the current splitscreen session (from start to end)
+ private InstanceId mLoggerSessionId;
+
+ // Drag info
+ private @SplitPosition int mDragEnterPosition;
+ private InstanceId mDragEnterSessionId;
+
+ // For deduping async events
+ private int mLastMainStagePosition = -1;
+ private int mLastMainStageUid = -1;
+ private int mLastSideStagePosition = -1;
+ private int mLastSideStageUid = -1;
+ private float mLastSplitRatio = -1f;
+
+ public SplitscreenEventLogger() {
+ mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE);
+ }
+
+ /**
+ * Return whether a splitscreen session has started.
+ */
+ public boolean hasStartedSession() {
+ return mLoggerSessionId != null;
+ }
+
+ /**
+ * May be called before logEnter() to indicate that the session was started from a drag.
+ */
+ public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) {
+ mDragEnterPosition = position;
+ mDragEnterSessionId = dragSessionId;
+ }
+
+ /**
+ * Logs when the user enters splitscreen.
+ */
+ public void logEnter(float splitRatio,
+ @SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid,
+ boolean isLandscape) {
+ mLoggerSessionId = mIdSequence.newInstanceId();
+ int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED
+ ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape)
+ : SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+ updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid);
+ updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid);
+ updateSplitRatioState(splitRatio);
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__ENTER,
+ enterReason,
+ 0 /* exitReason */,
+ splitRatio,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the user exits splitscreen. Only one of the main or side stages should be
+ * specified to indicate which position was focused as a part of exiting (both can be unset).
+ */
+ public void logExit(int exitReason, @SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if ((mainStagePosition != SPLIT_POSITION_UNDEFINED
+ && sideStagePosition != SPLIT_POSITION_UNDEFINED)
+ || (mainStageUid != 0 && sideStageUid != 0)) {
+ throw new IllegalArgumentException("Only main or side stage should be set");
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__EXIT,
+ 0 /* enterReason */,
+ exitReason,
+ 0f /* splitRatio */,
+ getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid,
+ getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+
+ // Reset states
+ mLoggerSessionId = null;
+ mDragEnterPosition = SPLIT_POSITION_UNDEFINED;
+ mDragEnterSessionId = null;
+ mLastMainStagePosition = -1;
+ mLastMainStageUid = -1;
+ mLastSideStagePosition = -1;
+ mLastSideStageUid = -1;
+ }
+
+ /**
+ * Logs when an app in the main stage changes.
+ */
+ public void logMainStageAppChange(@SplitPosition int mainStagePosition, int mainStageUid,
+ boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (!updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition,
+ isLandscape), mainStageUid)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ 0 /* sideStagePosition */,
+ 0 /* sideStageUid */,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when an app in the side stage changes.
+ */
+ public void logSideStageAppChange(@SplitPosition int sideStagePosition, int sideStageUid,
+ boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (!updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition,
+ isLandscape), sideStageUid)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ 0 /* mainStagePosition */,
+ 0 /* mainStageUid */,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the splitscreen ratio changes.
+ */
+ public void logResize(float splitRatio) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (splitRatio <= 0f || splitRatio >= 1f) {
+ // Don't bother reporting resizes that end up dismissing the split, that will be logged
+ // via the exit event
+ return;
+ }
+ if (!updateSplitRatioState(splitRatio)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__RESIZE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ mLastSplitRatio,
+ 0 /* mainStagePosition */, 0 /* mainStageUid */,
+ 0 /* sideStagePosition */, 0 /* sideStageUid */,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the apps in splitscreen are swapped.
+ */
+ public void logSwap(@SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+
+ updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid);
+ updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid);
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__SWAP,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ private boolean updateMainStageState(int mainStagePosition, int mainStageUid) {
+ boolean changed = (mLastMainStagePosition != mainStagePosition)
+ || (mLastMainStageUid != mainStageUid);
+ if (!changed) {
+ return false;
+ }
+
+ mLastMainStagePosition = mainStagePosition;
+ mLastMainStageUid = mainStageUid;
+ return true;
+ }
+
+ private boolean updateSideStageState(int sideStagePosition, int sideStageUid) {
+ boolean changed = (mLastSideStagePosition != sideStagePosition)
+ || (mLastSideStageUid != sideStageUid);
+ if (!changed) {
+ return false;
+ }
+
+ mLastSideStagePosition = sideStagePosition;
+ mLastSideStageUid = sideStageUid;
+ return true;
+ }
+
+ private boolean updateSplitRatioState(float splitRatio) {
+ boolean changed = Float.compare(mLastSplitRatio, splitRatio) != 0;
+ if (!changed) {
+ return false;
+ }
+
+ mLastSplitRatio = splitRatio;
+ return true;
+ }
+
+ public int getDragEnterReasonFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_BOTTOM;
+ }
+ }
+
+ private int getMainStagePositionFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (position == SPLIT_POSITION_UNDEFINED) {
+ return 0;
+ }
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__BOTTOM;
+ }
+ }
+
+ private int getSideStagePositionFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (position == SPLIT_POSITION_UNDEFINED) {
+ return 0;
+ }
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__BOTTOM;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
new file mode 100644
index 0000000..2f75f8b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
@@ -0,0 +1,1325 @@
+/*
+ * 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 com.android.wm.shell.stagesplit;
+
+import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.transitTypeToString;
+import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
+
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_SIDE;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.stagesplit.SplitScreen.stageTypeToString;
+import static com.android.wm.shell.stagesplit.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+import static com.android.wm.shell.transition.Transitions.isClosingType;
+import static com.android.wm.shell.transition.Transitions.isOpeningType;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
+import android.window.DisplayAreaInfo;
+import android.window.RemoteTransition;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+import com.android.wm.shell.common.split.SplitWindowManager;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.transition.Transitions;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import javax.inject.Provider;
+
+/**
+ * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
+ * {@link SideStage} stages.
+ * Some high-level rules:
+ * - The {@link StageCoordinator} is only considered active if the {@link SideStage} contains at
+ * least one child task.
+ * - The {@link MainStage} should only have children if the coordinator is active.
+ * - The {@link SplitLayout} divider is only visible if both the {@link MainStage}
+ * and {@link SideStage} are visible.
+ * - The {@link MainStage} configuration is fullscreen when the {@link SideStage} isn't visible.
+ * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
+ * {@link #onStageHasChildrenChanged(StageListenerImpl).}
+ */
+class StageCoordinator implements SplitLayout.SplitLayoutHandler,
+ RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler {
+
+ private static final String TAG = StageCoordinator.class.getSimpleName();
+
+ /** internal value for mDismissTop that represents no dismiss */
+ private static final int NO_DISMISS = -2;
+
+ private final SurfaceSession mSurfaceSession = new SurfaceSession();
+
+ private final MainStage mMainStage;
+ private final StageListenerImpl mMainStageListener = new StageListenerImpl();
+ private final StageTaskUnfoldController mMainUnfoldController;
+ private final SideStage mSideStage;
+ private final StageListenerImpl mSideStageListener = new StageListenerImpl();
+ private final StageTaskUnfoldController mSideUnfoldController;
+ @SplitPosition
+ private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
+
+ private final int mDisplayId;
+ private SplitLayout mSplitLayout;
+ private boolean mDividerVisible;
+ private final SyncTransactionQueue mSyncQueue;
+ private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private DisplayAreaInfo mDisplayAreaInfo;
+ private final Context mContext;
+ private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+ private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final SplitScreenTransitions mSplitTransitions;
+ private final SplitscreenEventLogger mLogger;
+ private boolean mExitSplitScreenOnHide;
+ private boolean mKeyguardOccluded;
+
+ // TODO(b/187041611): remove this flag after totally deprecated legacy split
+ /** Whether the device is supporting legacy split or not. */
+ private boolean mUseLegacySplit;
+
+ @SplitScreen.StageType private int mDismissTop = NO_DISMISS;
+
+ /** The target stage to dismiss to when unlock after folded. */
+ @SplitScreen.StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+
+ private final Runnable mOnTransitionAnimationComplete = () -> {
+ // If still playing, let it finish.
+ if (!isSplitScreenVisible()) {
+ // Update divider state after animation so that it is still around and positioned
+ // properly for the animation itself.
+ setDividerVisibility(false);
+ mSplitLayout.resetDividerPosition();
+ }
+ mDismissTop = NO_DISMISS;
+ };
+
+ private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
+ new SplitWindowManager.ParentContainerCallbacks() {
+ @Override
+ public void attachToParentSurface(SurfaceControl.Builder b) {
+ mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
+ }
+
+ @Override
+ public void onLeashReady(SurfaceControl leash) {
+ mSyncQueue.runInSync(t -> applyDividerVisibility(t));
+ }
+ };
+
+ StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, Transitions transitions,
+ TransactionPool transactionPool, SplitscreenEventLogger logger,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mContext = context;
+ mDisplayId = displayId;
+ mSyncQueue = syncQueue;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mTaskOrganizer = taskOrganizer;
+ mLogger = logger;
+ mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+
+ mMainStage = new MainStage(
+ mTaskOrganizer,
+ mDisplayId,
+ mMainStageListener,
+ mSyncQueue,
+ mSurfaceSession,
+ mMainUnfoldController);
+ mSideStage = new SideStage(
+ mContext,
+ mTaskOrganizer,
+ mDisplayId,
+ mSideStageListener,
+ mSyncQueue,
+ mSurfaceSession,
+ mSideUnfoldController);
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);
+ mRootTDAOrganizer.registerListener(displayId, this);
+ final DeviceStateManager deviceStateManager =
+ mContext.getSystemService(DeviceStateManager.class);
+ deviceStateManager.registerCallback(taskOrganizer.getExecutor(),
+ new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));
+ mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
+ mOnTransitionAnimationComplete);
+ transitions.addHandler(this);
+ }
+
+ @VisibleForTesting
+ StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
+ MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
+ Transitions transitions, TransactionPool transactionPool,
+ SplitscreenEventLogger logger,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mContext = context;
+ mDisplayId = displayId;
+ mSyncQueue = syncQueue;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mTaskOrganizer = taskOrganizer;
+ mMainStage = mainStage;
+ mSideStage = sideStage;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mRootTDAOrganizer.registerListener(displayId, this);
+ mSplitLayout = splitLayout;
+ mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
+ mOnTransitionAnimationComplete);
+ mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mLogger = logger;
+ transitions.addHandler(this);
+ }
+
+ @VisibleForTesting
+ SplitScreenTransitions getSplitTransitions() {
+ return mSplitTransitions;
+ }
+
+ boolean isSplitScreenVisible() {
+ return mSideStageListener.mVisible && mMainStageListener.mVisible;
+ }
+
+ boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+ @SplitPosition int sideStagePosition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(sideStagePosition, wct);
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.addTask(task, getSideStageBounds(), wct);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(null /* layout */, t));
+ return true;
+ }
+
+ boolean removeFromSideStage(int taskId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ /**
+ * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the
+ * {@link SideStage} no longer has children.
+ */
+ final boolean result = mSideStage.removeTask(taskId,
+ mMainStage.isActive() ? mMainStage.mRootTaskInfo.token : null,
+ wct);
+ mTaskOrganizer.applyTransaction(wct);
+ return result;
+ }
+
+ void setSideStageOutline(boolean enable) {
+ mSideStage.enableOutline(enable);
+ }
+
+ /** Starts 2 tasks in one transition. */
+ void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
+ @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ @Nullable RemoteTransition remoteTransition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mainOptions = mainOptions != null ? mainOptions : new Bundle();
+ sideOptions = sideOptions != null ? sideOptions : new Bundle();
+ setSideStagePosition(sidePosition, wct);
+
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+
+ // Make sure the launch options will put tasks in the corresponding split roots
+ addActivityOptions(mainOptions, mMainStage);
+ addActivityOptions(sideOptions, mSideStage);
+
+ // Add task launch requests
+ wct.startTask(mainTaskId, mainOptions);
+ wct.startTask(sideTaskId, sideOptions);
+
+ mSplitTransitions.startEnterTransition(
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this);
+ }
+
+ /** Starts 2 tasks in one legacy transition. */
+ void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ RemoteAnimationAdapter adapter) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Need to add another wrapper here in shell so that we can inject the divider bar
+ // and also manage the process elevation via setRunningRemote
+ IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+ RemoteAnimationTarget[] augmentedNonApps =
+ new RemoteAnimationTarget[nonApps.length + 1];
+ for (int i = 0; i < nonApps.length; ++i) {
+ augmentedNonApps[i] = nonApps[i];
+ }
+ augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget();
+ try {
+ ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
+ adapter.getCallingApplication());
+ adapter.getRunner().onAnimationStart(transit, apps, wallpapers, nonApps,
+ finishedCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ try {
+ adapter.getRunner().onAnimationCancelled();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+ };
+ RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
+ wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
+
+ if (mainOptions == null) {
+ mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
+ } else {
+ ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
+ mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+ }
+
+ sideOptions = sideOptions != null ? sideOptions : new Bundle();
+ setSideStagePosition(sidePosition, wct);
+
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+
+ // Make sure the launch options will put tasks in the corresponding split roots
+ addActivityOptions(mainOptions, mMainStage);
+ addActivityOptions(sideOptions, mSideStage);
+
+ // Add task launch requests
+ wct.startTask(mainTaskId, mainOptions);
+ wct.startTask(sideTaskId, sideOptions);
+
+ // Using legacy transitions, so we can't use blast sync since it conflicts.
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ public void startIntent(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @androidx.annotation.Nullable Bundle options,
+ @Nullable RemoteTransition remoteTransition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = resolveStartStage(stage, position, options, wct);
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ mSplitTransitions.startEnterTransition(
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
+ }
+
+ Bundle resolveStartStage(@SplitScreen.StageType int stage,
+ @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
+ @androidx.annotation.Nullable WindowContainerTransaction wct) {
+ switch (stage) {
+ case STAGE_TYPE_UNDEFINED: {
+ // Use the stage of the specified position is valid.
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ if (position == getSideStagePosition()) {
+ options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct);
+ } else {
+ options = resolveStartStage(STAGE_TYPE_MAIN, position, options, wct);
+ }
+ } else {
+ // Exit split-screen and launch fullscreen since stage wasn't specified.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ }
+ break;
+ }
+ case STAGE_TYPE_SIDE: {
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ setSideStagePosition(position, wct);
+ } else {
+ position = getSideStagePosition();
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ updateActivityOptions(options, position);
+ break;
+ }
+ case STAGE_TYPE_MAIN: {
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ // Set the side stage opposite of what we want to the main stage.
+ final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
+ setSideStagePosition(sideStagePosition, wct);
+ } else {
+ position = getMainStagePosition();
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ updateActivityOptions(options, position);
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown stage=" + stage);
+ }
+
+ return options;
+ }
+
+ @SplitPosition
+ int getSideStagePosition() {
+ return mSideStagePosition;
+ }
+
+ @SplitPosition
+ int getMainStagePosition() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
+ }
+
+ void setSideStagePosition(@SplitPosition int sideStagePosition,
+ @Nullable WindowContainerTransaction wct) {
+ setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
+ }
+
+ private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
+ @Nullable WindowContainerTransaction wct) {
+ if (mSideStagePosition == sideStagePosition) return;
+ mSideStagePosition = sideStagePosition;
+ sendOnStagePositionChanged();
+
+ if (mSideStageListener.mVisible && updateBounds) {
+ if (wct == null) {
+ // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
+ onLayoutChanged(mSplitLayout);
+ } else {
+ updateWindowBounds(mSplitLayout, wct);
+ updateUnfoldBounds();
+ }
+ }
+ }
+
+ void setSideStageVisibility(boolean visible) {
+ if (mSideStageListener.mVisible == visible) return;
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mSideStage.setVisibility(visible, wct);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ void onKeyguardOccludedChanged(boolean occluded) {
+ // Do not exit split directly, because it needs to wait for task info update to determine
+ // which task should remain on top after split dismissed.
+ mKeyguardOccluded = occluded;
+ }
+
+ void onKeyguardVisibilityChanged(boolean showing) {
+ if (!showing && mMainStage.isActive()
+ && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+ exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED);
+ }
+ }
+
+ void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ mExitSplitScreenOnHide = exitSplitScreenOnHide;
+ }
+
+ void exitSplitScreen(int toTopTaskId, int exitReason) {
+ StageTaskListener childrenToTop = null;
+ if (mMainStage.containsTask(toTopTaskId)) {
+ childrenToTop = mMainStage;
+ } else if (mSideStage.containsTask(toTopTaskId)) {
+ childrenToTop = mSideStage;
+ }
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (childrenToTop != null) {
+ childrenToTop.reorderChild(toTopTaskId, true /* onTop */, wct);
+ }
+ applyExitSplitScreen(childrenToTop, wct, exitReason);
+ }
+
+ private void exitSplitScreen(StageTaskListener childrenToTop, int exitReason) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ applyExitSplitScreen(childrenToTop, wct, exitReason);
+ }
+
+ private void applyExitSplitScreen(
+ StageTaskListener childrenToTop,
+ WindowContainerTransaction wct, int exitReason) {
+ mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
+ mMainStage.deactivate(wct, childrenToTop == mMainStage);
+ mTaskOrganizer.applyTransaction(wct);
+ mSyncQueue.runInSync(t -> t
+ .setWindowCrop(mMainStage.mRootLeash, null)
+ .setWindowCrop(mSideStage.mRootLeash, null));
+ // Hide divider and reset its position.
+ setDividerVisibility(false);
+ mSplitLayout.resetDividerPosition();
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ if (childrenToTop != null) {
+ logExitToStage(exitReason, childrenToTop == mMainStage);
+ } else {
+ logExit(exitReason);
+ }
+ }
+
+ /**
+ * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
+ * an existing WindowContainerTransaction (rather than applying immediately). This is intended
+ * to be used when exiting split might be bundled with other window operations.
+ */
+ void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop,
+ @NonNull WindowContainerTransaction wct) {
+ mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
+ mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
+ }
+
+ void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
+ outTopOrLeftBounds.set(mSplitLayout.getBounds1());
+ outBottomOrRightBounds.set(mSplitLayout.getBounds2());
+ }
+
+ private void addActivityOptions(Bundle opts, StageTaskListener stage) {
+ opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
+ }
+
+ void updateActivityOptions(Bundle opts, @SplitPosition int position) {
+ addActivityOptions(opts, position == mSideStagePosition ? mSideStage : mMainStage);
+ }
+
+ void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ if (mListeners.contains(listener)) return;
+ mListeners.add(listener);
+ sendStatusToListener(listener);
+ }
+
+ void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mListeners.remove(listener);
+ }
+
+ void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
+ listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+ listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+ listener.onSplitVisibilityChanged(isSplitScreenVisible());
+ mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
+ mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
+ }
+
+ private void sendOnStagePositionChanged() {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final SplitScreen.SplitScreenListener l = mListeners.get(i);
+ l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+ l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+ }
+ }
+
+ private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
+ boolean present, boolean visible) {
+ int stage;
+ if (present) {
+ stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+ } else {
+ // No longer on any stage
+ stage = STAGE_TYPE_UNDEFINED;
+ }
+ if (stage == STAGE_TYPE_MAIN) {
+ mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ } else {
+ mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onTaskStageChanged(taskId, stage, visible);
+ }
+ }
+
+ private void sendSplitVisibilityChanged() {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final SplitScreen.SplitScreenListener l = mListeners.get(i);
+ l.onSplitVisibilityChanged(mDividerVisible);
+ }
+
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ }
+ }
+
+ private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
+ if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
+ mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Make the stages adjacent to each other so they occlude what's behind them.
+ wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+
+ // Only sets side stage as launch-adjacent-flag-root when the device is not using legacy
+ // split to prevent new split behavior confusing users.
+ if (!mUseLegacySplit) {
+ wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
+ }
+
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ private void onStageRootTaskVanished(StageListenerImpl stageListener) {
+ if (stageListener == mMainStageListener || stageListener == mSideStageListener) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Deactivate the main stage if it no longer has a root task.
+ mMainStage.deactivate(wct);
+
+ if (!mUseLegacySplit) {
+ wct.clearLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
+ }
+
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ private void setDividerVisibility(boolean visible) {
+ if (mDividerVisible == visible) return;
+ mDividerVisible = visible;
+ if (visible) {
+ mSplitLayout.init();
+ updateUnfoldBounds();
+ } else {
+ mSplitLayout.release();
+ }
+ sendSplitVisibilityChanged();
+ }
+
+ private void onStageVisibilityChanged(StageListenerImpl stageListener) {
+ final boolean sideStageVisible = mSideStageListener.mVisible;
+ final boolean mainStageVisible = mMainStageListener.mVisible;
+ final boolean bothStageVisible = sideStageVisible && mainStageVisible;
+ final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible;
+ final boolean sameVisibility = sideStageVisible == mainStageVisible;
+ // Only add or remove divider when both visible or both invisible to avoid sometimes we only
+ // got one stage visibility changed for a moment and it will cause flicker.
+ if (sameVisibility) {
+ setDividerVisibility(bothStageVisible);
+ }
+
+ if (bothStageInvisible) {
+ if (mExitSplitScreenOnHide
+ // Don't dismiss staged split when both stages are not visible due to sleeping display,
+ // like the cases keyguard showing or screen off.
+ || (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) {
+ exitSplitScreen(null /* childrenToTop */,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
+ }
+ } else if (mKeyguardOccluded) {
+ // At least one of the stages is visible while keyguard occluded. Dismiss split because
+ // there's show-when-locked activity showing on top of keyguard. Also make sure the
+ // task contains show-when-locked activity remains on top after split dismissed.
+ final StageTaskListener toTop =
+ mainStageVisible ? mMainStage : (sideStageVisible ? mSideStage : null);
+ exitSplitScreen(toTop, SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP);
+ }
+
+ mSyncQueue.runInSync(t -> {
+ // Same above, we only set root tasks and divider leash visibility when both stage
+ // change to visible or invisible to avoid flicker.
+ if (sameVisibility) {
+ t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
+ .setVisibility(mMainStage.mRootLeash, bothStageVisible);
+ applyDividerVisibility(t);
+ applyOutlineVisibility(t);
+ }
+ });
+ }
+
+ private void applyDividerVisibility(SurfaceControl.Transaction t) {
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ if (dividerLeash == null) {
+ return;
+ }
+
+ if (mDividerVisible) {
+ t.show(dividerLeash)
+ .setLayer(dividerLeash, SPLIT_DIVIDER_LAYER)
+ .setPosition(dividerLeash,
+ mSplitLayout.getDividerBounds().left,
+ mSplitLayout.getDividerBounds().top);
+ } else {
+ t.hide(dividerLeash);
+ }
+ }
+
+ private void applyOutlineVisibility(SurfaceControl.Transaction t) {
+ final SurfaceControl outlineLeash = mSideStage.getOutlineLeash();
+ if (outlineLeash == null) {
+ return;
+ }
+
+ if (mDividerVisible) {
+ t.show(outlineLeash).setLayer(outlineLeash, SPLIT_DIVIDER_LAYER);
+ } else {
+ t.hide(outlineLeash);
+ }
+ }
+
+ private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
+ final boolean hasChildren = stageListener.mHasChildren;
+ final boolean isSideStage = stageListener == mSideStageListener;
+ if (!hasChildren) {
+ if (isSideStage && mMainStageListener.mVisible) {
+ // Exit to main stage if side stage no longer has children.
+ exitSplitScreen(mMainStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
+ } else if (!isSideStage && mSideStageListener.mVisible) {
+ // Exit to side stage if main stage no longer has children.
+ exitSplitScreen(mSideStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
+ }
+ } else if (isSideStage) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Make sure the main stage is active.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren
+ && mSideStageListener.mHasChildren) {
+ mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
+ getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+ }
+
+ @VisibleForTesting
+ IBinder onSnappedToDismissTransition(boolean mainStageToTop) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE, wct);
+ return mSplitTransitions.startSnapToDismiss(wct, this);
+ }
+
+ @Override
+ public void onSnappedToDismiss(boolean bottomOrRight) {
+ final boolean mainStageToTop =
+ bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
+ : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
+ if (ENABLE_SHELL_TRANSITIONS) {
+ onSnappedToDismissTransition(mainStageToTop);
+ return;
+ }
+ exitSplitScreen(mainStageToTop ? mMainStage : mSideStage,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER);
+ }
+
+ @Override
+ public void onDoubleTappedDivider() {
+ setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
+ mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+
+ @Override
+ public void onLayoutChanging(SplitLayout layout) {
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+ mSideStage.setOutlineVisibility(false);
+ }
+
+ @Override
+ public void onLayoutChanged(SplitLayout layout) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ updateWindowBounds(layout, wct);
+ updateUnfoldBounds();
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+ mSideStage.setOutlineVisibility(true);
+ mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
+ }
+
+ private void updateUnfoldBounds() {
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.onLayoutChanged(getMainStageBounds());
+ mSideUnfoldController.onLayoutChanged(getSideStageBounds());
+ }
+ }
+
+ /**
+ * Populates `wct` with operations that match the split windows to the current layout.
+ * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
+ */
+ private void updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
+ }
+
+ void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
+ bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer);
+ }
+
+ @Override
+ public int getSplitItemPosition(WindowContainerToken token) {
+ if (token == null) {
+ return SPLIT_POSITION_UNDEFINED;
+ }
+
+ if (token.equals(mMainStage.mRootTaskInfo.getToken())) {
+ return getMainStagePosition();
+ } else if (token.equals(mSideStage.mRootTaskInfo.getToken())) {
+ return getSideStagePosition();
+ }
+
+ return SPLIT_POSITION_UNDEFINED;
+ }
+
+ @Override
+ public void onLayoutShifted(int offsetX, int offsetY, SplitLayout layout) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ layout.applyLayoutShifted(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
+ bottomRightStage.mRootTaskInfo);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ @Override
+ public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) {
+ mDisplayAreaInfo = displayAreaInfo;
+ if (mSplitLayout == null) {
+ mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
+ mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
+ mDisplayImeController, mTaskOrganizer);
+ mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
+
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.init();
+ mSideUnfoldController.init();
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
+ throw new IllegalStateException("Well that was unexpected...");
+ }
+
+ @Override
+ public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) {
+ mDisplayAreaInfo = displayAreaInfo;
+ if (mSplitLayout != null
+ && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
+ && mMainStage.isActive()) {
+ onLayoutChanged(mSplitLayout);
+ }
+ }
+
+ private void onFoldedStateChanged(boolean folded) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ if (!folded) return;
+
+ if (mMainStage.isFocused()) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
+ } else if (mSideStage.isFocused()) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
+ }
+ }
+
+ private Rect getSideStageBounds() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
+ }
+
+ private Rect getMainStageBounds() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
+ }
+
+ /**
+ * Get the stage that should contain this `taskInfo`. The stage doesn't necessarily contain
+ * this task (yet) so this can also be used to identify which stage to put a task into.
+ */
+ private StageTaskListener getStageOfTask(ActivityManager.RunningTaskInfo taskInfo) {
+ // TODO(b/184679596): Find a way to either include task-org information in the transition,
+ // or synchronize task-org callbacks so we can use stage.containsTask
+ if (mMainStage.mRootTaskInfo != null
+ && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) {
+ return mMainStage;
+ } else if (mSideStage.mRootTaskInfo != null
+ && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) {
+ return mSideStage;
+ }
+ return null;
+ }
+
+ @SplitScreen.StageType
+ private int getStageType(StageTaskListener stage) {
+ return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ }
+
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @Nullable TransitionRequestInfo request) {
+ final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
+ if (triggerTask == null) {
+ // still want to monitor everything while in split-screen, so return non-null.
+ return isSplitScreenVisible() ? new WindowContainerTransaction() : null;
+ }
+
+ WindowContainerTransaction out = null;
+ final @WindowManager.TransitionType int type = request.getType();
+ if (isSplitScreenVisible()) {
+ // try to handle everything while in split-screen, so return a WCT even if it's empty.
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split"
+ + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
+ + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type),
+ mMainStage.getChildCount(), mSideStage.getChildCount());
+ out = new WindowContainerTransaction();
+ final StageTaskListener stage = getStageOfTask(triggerTask);
+ if (stage != null) {
+ // dismiss split if the last task in one of the stages is going away
+ if (isClosingType(type) && stage.getChildCount() == 1) {
+ // The top should be the opposite side that is closing:
+ mDismissTop = getStageType(stage) == STAGE_TYPE_MAIN
+ ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+ }
+ } else {
+ if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) {
+ // Going home so dismiss both.
+ mDismissTop = STAGE_TYPE_UNDEFINED;
+ }
+ }
+ if (mDismissTop != NO_DISMISS) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ + " deduced Dismiss from request. toTop=%s",
+ stageTypeToString(mDismissTop));
+ prepareExitSplitScreen(mDismissTop, out);
+ mSplitTransitions.mPendingDismiss = transition;
+ }
+ } else {
+ // Not in split mode, so look for an open into a split stage just so we can whine and
+ // complain about how this isn't a supported operation.
+ if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)) {
+ if (getStageOfTask(triggerTask) != null) {
+ throw new IllegalStateException("Entering split implicitly with only one task"
+ + " isn't supported.");
+ }
+ }
+ }
+ return out;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (transition != mSplitTransitions.mPendingDismiss
+ && transition != mSplitTransitions.mPendingEnter) {
+ // Not entering or exiting, so just do some house-keeping and validation.
+
+ // If we're not in split-mode, just abort so something else can handle it.
+ if (!isSplitScreenVisible()) return false;
+
+ for (int iC = 0; iC < info.getChanges().size(); ++iC) {
+ final TransitionInfo.Change change = info.getChanges().get(iC);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || !taskInfo.hasParentTask()) continue;
+ final StageTaskListener stage = getStageOfTask(taskInfo);
+ if (stage == null) continue;
+ if (isOpeningType(change.getMode())) {
+ if (!stage.containsTask(taskInfo.taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called"
+ + " with " + taskInfo.taskId + " before startAnimation().");
+ }
+ } else if (isClosingType(change.getMode())) {
+ if (stage.containsTask(taskInfo.taskId)) {
+ Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
+ + " with " + taskInfo.taskId + " before startAnimation().");
+ }
+ }
+ }
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+ // TODO(shell-transitions): Implement a fallback behavior for now.
+ throw new IllegalStateException("Somehow removed the last task in a stage"
+ + " outside of a proper transition");
+ // This can happen in some pathological cases. For example:
+ // 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C]
+ // 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time
+ // In this case, the result *should* be that we leave split.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ }
+
+ // Use normal animations.
+ return false;
+ }
+
+ boolean shouldAnimate = true;
+ if (mSplitTransitions.mPendingEnter == transition) {
+ shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
+ } else if (mSplitTransitions.mPendingDismiss == transition) {
+ shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction);
+ }
+ if (!shouldAnimate) return false;
+
+ mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
+ finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+ return true;
+ }
+
+ private boolean startPendingEnterAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ if (info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN) {
+ // First, verify that we actually have opened 2 apps in split.
+ TransitionInfo.Change mainChild = null;
+ TransitionInfo.Change sideChild = null;
+ for (int iC = 0; iC < info.getChanges().size(); ++iC) {
+ final TransitionInfo.Change change = info.getChanges().get(iC);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || !taskInfo.hasParentTask()) continue;
+ final @SplitScreen.StageType int stageType = getStageType(getStageOfTask(taskInfo));
+ if (stageType == STAGE_TYPE_MAIN) {
+ mainChild = change;
+ } else if (stageType == STAGE_TYPE_SIDE) {
+ sideChild = change;
+ }
+ }
+ if (mainChild == null || sideChild == null) {
+ throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+ + " 2 tasks in transition. Possibly one of them failed to launch");
+ // TODO: fallback logic. Probably start a new transition to exit split before
+ // applying anything here. Ideally consolidate with transition-merging.
+ }
+
+ // Update local states (before animating).
+ setDividerVisibility(true);
+ setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */,
+ null /* wct */);
+ setSplitsVisible(true);
+
+ addDividerBarToTransition(info, t, true /* show */);
+
+ // Make some noise if things aren't totally expected. These states shouldn't effect
+ // transitions locally, but remotes (like Launcher) may get confused if they were
+ // depending on listener callbacks. This can happen because task-organizer callbacks
+ // aren't serialized with transition callbacks.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
+ + " to have been called with " + mainChild.getTaskInfo().taskId
+ + " before startAnimation().");
+ }
+ if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
+ + " to have been called with " + sideChild.getTaskInfo().taskId
+ + " before startAnimation().");
+ }
+ return true;
+ } else {
+ // TODO: other entry method animations
+ throw new RuntimeException("Unsupported split-entry");
+ }
+ }
+
+ private boolean startPendingDismissAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ // Make some noise if things aren't totally expected. These states shouldn't effect
+ // transitions locally, but remotes (like Launcher) may get confused if they were
+ // depending on listener callbacks. This can happen because task-organizer callbacks
+ // aren't serialized with transition callbacks.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ if (mMainStage.getChildCount() != 0) {
+ final StringBuilder tasksLeft = new StringBuilder();
+ for (int i = 0; i < mMainStage.getChildCount(); ++i) {
+ tasksLeft.append(i != 0 ? ", " : "");
+ tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i));
+ }
+ Log.w(TAG, "Expected onTaskVanished on " + mMainStage
+ + " to have been called with [" + tasksLeft.toString()
+ + "] before startAnimation().");
+ }
+ if (mSideStage.getChildCount() != 0) {
+ final StringBuilder tasksLeft = new StringBuilder();
+ for (int i = 0; i < mSideStage.getChildCount(); ++i) {
+ tasksLeft.append(i != 0 ? ", " : "");
+ tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i));
+ }
+ Log.w(TAG, "Expected onTaskVanished on " + mSideStage
+ + " to have been called with [" + tasksLeft.toString()
+ + "] before startAnimation().");
+ }
+
+ // Update local states.
+ setSplitsVisible(false);
+ // Wait until after animation to update divider
+
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Reset crops so they don't interfere with subsequent launches
+ t.setWindowCrop(mMainStage.mRootLeash, null);
+ t.setWindowCrop(mSideStage.mRootLeash, null);
+ }
+
+ if (mDismissTop == STAGE_TYPE_UNDEFINED) {
+ // Going home (dismissing both splits)
+
+ // TODO: Have a proper remote for this. Until then, though, reset state and use the
+ // normal animation stuff (which falls back to the normal launcher remote).
+ t.hide(mSplitLayout.getDividerLeash());
+ setDividerVisibility(false);
+ mSplitTransitions.mPendingDismiss = null;
+ return false;
+ }
+
+ addDividerBarToTransition(info, t, false /* show */);
+ // We're dismissing split by moving the other one to fullscreen.
+ // Since we don't have any animations for this yet, just use the internal example
+ // animations.
+ return true;
+ }
+
+ private void addDividerBarToTransition(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, boolean show) {
+ final SurfaceControl leash = mSplitLayout.getDividerLeash();
+ final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
+ final Rect bounds = mSplitLayout.getDividerBounds();
+ barChange.setStartAbsBounds(bounds);
+ barChange.setEndAbsBounds(bounds);
+ barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
+ barChange.setFlags(FLAG_IS_DIVIDER_BAR);
+ // Technically this should be order-0, but this is running after layer assignment
+ // and it's a special case, so just add to end.
+ info.addChange(barChange);
+ // Be default, make it visible. The remote animator can adjust alpha if it plans to animate.
+ if (show) {
+ t.setAlpha(leash, 1.f);
+ t.setLayer(leash, SPLIT_DIVIDER_LAYER);
+ t.setPosition(leash, bounds.left, bounds.top);
+ t.show(leash);
+ }
+ }
+
+ RemoteAnimationTarget getDividerBarLegacyTarget() {
+ final Rect bounds = mSplitLayout.getDividerBounds();
+ return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
+ mSplitLayout.getDividerLeash(), false /* isTranslucent */, null /* clipRect */,
+ null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
+ new android.graphics.Point(0, 0) /* position */, bounds, bounds,
+ new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
+ null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
+ }
+
+ RemoteAnimationTarget getOutlineLegacyTarget() {
+ final Rect bounds = mSideStage.mRootTaskInfo.configuration.windowConfiguration.getBounds();
+ // Leverage TYPE_DOCK_DIVIDER type when wrapping outline remote animation target in order to
+ // distinguish as a split auxiliary target in Launcher.
+ return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
+ mSideStage.getOutlineLeash(), false /* isTranslucent */, null /* clipRect */,
+ null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
+ new android.graphics.Point(0, 0) /* position */, bounds, bounds,
+ new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
+ null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ final String childPrefix = innerPrefix + " ";
+ pw.println(prefix + TAG + " mDisplayId=" + mDisplayId);
+ pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
+ pw.println(innerPrefix + "MainStage");
+ pw.println(childPrefix + "isActive=" + mMainStage.isActive());
+ mMainStageListener.dump(pw, childPrefix);
+ pw.println(innerPrefix + "SideStage");
+ mSideStageListener.dump(pw, childPrefix);
+ pw.println(innerPrefix + "mSplitLayout=" + mSplitLayout);
+ }
+
+ /**
+ * Directly set the visibility of both splits. This assumes hasChildren matches visibility.
+ * This is intended for batch use, so it assumes other state management logic is already
+ * handled.
+ */
+ private void setSplitsVisible(boolean visible) {
+ mMainStageListener.mVisible = mSideStageListener.mVisible = visible;
+ mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
+ }
+
+ /**
+ * Sets drag info to be logged when splitscreen is next entered.
+ */
+ public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ mLogger.enterRequestedByDrag(position, dragSessionId);
+ }
+
+ /**
+ * Logs the exit of splitscreen.
+ */
+ private void logExit(int exitReason) {
+ mLogger.logExit(exitReason,
+ SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */,
+ SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */,
+ mSplitLayout.isLandscape());
+ }
+
+ /**
+ * Logs the exit of splitscreen to a specific stage. This must be called before the exit is
+ * executed.
+ */
+ private void logExitToStage(int exitReason, boolean toMainStage) {
+ mLogger.logExit(exitReason,
+ toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED,
+ toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
+ !toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED,
+ !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
+ mSplitLayout.isLandscape());
+ }
+
+ class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
+ boolean mHasRootTask = false;
+ boolean mVisible = false;
+ boolean mHasChildren = false;
+
+ @Override
+ public void onRootTaskAppeared() {
+ mHasRootTask = true;
+ StageCoordinator.this.onStageRootTaskAppeared(this);
+ }
+
+ @Override
+ public void onStatusChanged(boolean visible, boolean hasChildren) {
+ if (!mHasRootTask) return;
+
+ if (mHasChildren != hasChildren) {
+ mHasChildren = hasChildren;
+ StageCoordinator.this.onStageHasChildrenChanged(this);
+ }
+ if (mVisible != visible) {
+ mVisible = visible;
+ StageCoordinator.this.onStageVisibilityChanged(this);
+ }
+ }
+
+ @Override
+ public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) {
+ StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible);
+ }
+
+ @Override
+ public void onRootTaskVanished() {
+ reset();
+ StageCoordinator.this.onStageRootTaskVanished(this);
+ }
+
+ @Override
+ public void onNoLongerSupportMultiWindow() {
+ if (mMainStage.isActive()) {
+ StageCoordinator.this.exitSplitScreen(null /* childrenToTop */,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+ }
+ }
+
+ private void reset() {
+ mHasRootTask = false;
+ mVisible = false;
+ mHasChildren = false;
+ }
+
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + "mHasRootTask=" + mHasRootTask);
+ pw.println(prefix + "mVisible=" + mVisible);
+ pw.println(prefix + "mHasChildren=" + mHasChildren);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
new file mode 100644
index 0000000..8b36c94
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
@@ -0,0 +1,288 @@
+/*
+ * 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 com.android.wm.shell.stagesplit;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+
+import android.annotation.CallSuper;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SurfaceUtils;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import java.io.PrintWriter;
+
+/**
+ * Base class that handle common task org. related for split-screen stages.
+ * Note that this class and its sub-class do not directly perform hierarchy operations.
+ * They only serve to hold a collection of tasks and provide APIs like
+ * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
+ * to perform operations in-sync with other containers.
+ *
+ * @see StageCoordinator
+ */
+class StageTaskListener implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = StageTaskListener.class.getSimpleName();
+
+ protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
+ protected static final int[] CONTROLLED_WINDOWING_MODES =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
+ protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
+
+ /** Callback interface for listening to changes in a split-screen stage. */
+ public interface StageListenerCallbacks {
+ void onRootTaskAppeared();
+
+ void onStatusChanged(boolean visible, boolean hasChildren);
+
+ void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
+
+ void onRootTaskVanished();
+ void onNoLongerSupportMultiWindow();
+ }
+
+ private final StageListenerCallbacks mCallbacks;
+ private final SurfaceSession mSurfaceSession;
+ protected final SyncTransactionQueue mSyncQueue;
+
+ protected ActivityManager.RunningTaskInfo mRootTaskInfo;
+ protected SurfaceControl mRootLeash;
+ protected SurfaceControl mDimLayer;
+ protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
+ private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
+
+ private final StageTaskUnfoldController mStageTaskUnfoldController;
+
+ StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ mCallbacks = callbacks;
+ mSyncQueue = syncQueue;
+ mSurfaceSession = surfaceSession;
+ mStageTaskUnfoldController = stageTaskUnfoldController;
+ taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
+ }
+
+ int getChildCount() {
+ return mChildrenTaskInfo.size();
+ }
+
+ boolean containsTask(int taskId) {
+ return mChildrenTaskInfo.contains(taskId);
+ }
+
+ /**
+ * Returns the top activity uid for the top child task.
+ */
+ int getTopChildTaskUid() {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
+ if (info.topActivityInfo == null) {
+ continue;
+ }
+ return info.topActivityInfo.applicationInfo.uid;
+ }
+ return 0;
+ }
+
+ /** @return {@code true} if this listener contains the currently focused task. */
+ boolean isFocused() {
+ if (mRootTaskInfo == null) {
+ return false;
+ }
+
+ if (mRootTaskInfo.isFocused) {
+ return true;
+ }
+
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ if (mChildrenTaskInfo.valueAt(i).isFocused) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (mRootTaskInfo == null && !taskInfo.hasParentTask()) {
+ mRootLeash = leash;
+ mRootTaskInfo = taskInfo;
+ mCallbacks.onRootTaskAppeared();
+ sendStatusChanged();
+ mSyncQueue.runInSync(t -> {
+ t.hide(mRootLeash);
+ mDimLayer =
+ SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession);
+ });
+ } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
+ final int taskId = taskInfo.taskId;
+ mChildrenLeashes.put(taskId, leash);
+ mChildrenTaskInfo.put(taskId, taskInfo);
+ updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
+ mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+
+ if (mStageTaskUnfoldController != null) {
+ mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
+ }
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (!taskInfo.supportsMultiWindow) {
+ // Leave split screen if the task no longer supports multi window.
+ mCallbacks.onNoLongerSupportMultiWindow();
+ return;
+ }
+ if (mRootTaskInfo.taskId == taskInfo.taskId) {
+ mRootTaskInfo = taskInfo;
+ } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
+ mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
+ taskInfo.isVisible);
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ updateChildTaskSurface(
+ taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
+ }
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ final int taskId = taskInfo.taskId;
+ if (mRootTaskInfo.taskId == taskId) {
+ mCallbacks.onRootTaskVanished();
+ mSyncQueue.runInSync(t -> t.remove(mDimLayer));
+ mRootTaskInfo = null;
+ } else if (mChildrenTaskInfo.contains(taskId)) {
+ mChildrenTaskInfo.remove(taskId);
+ mChildrenLeashes.remove(taskId);
+ mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+
+ if (mStageTaskUnfoldController != null) {
+ mStageTaskUnfoldController.onTaskVanished(taskInfo);
+ }
+ }
+
+ @Override
+ public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ if (mRootTaskInfo.taskId == taskId) {
+ b.setParent(mRootLeash);
+ } else if (mChildrenLeashes.contains(taskId)) {
+ b.setParent(mChildrenLeashes.get(taskId));
+ } else {
+ throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+ }
+ }
+
+ void setBounds(Rect bounds, WindowContainerTransaction wct) {
+ wct.setBounds(mRootTaskInfo.token, bounds);
+ }
+
+ void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
+ if (!containsTask(taskId)) {
+ return;
+ }
+ wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
+ }
+
+ void setVisibility(boolean visible, WindowContainerTransaction wct) {
+ wct.reorder(mRootTaskInfo.token, visible /* onTop */);
+ }
+
+ void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
+ @SplitScreen.StageType int stage) {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ int taskId = mChildrenTaskInfo.keyAt(i);
+ listener.onTaskStageChanged(taskId, stage,
+ mChildrenTaskInfo.get(taskId).isVisible);
+ }
+ }
+
+ private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl leash, boolean firstAppeared) {
+ final Point taskPositionInParent = taskInfo.positionInParent;
+ mSyncQueue.runInSync(t -> {
+ t.setWindowCrop(leash, null);
+ t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
+ if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) {
+ t.setAlpha(leash, 1f);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ t.show(leash);
+ }
+ });
+ }
+
+ private void sendStatusChanged() {
+ mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0);
+ }
+
+ @Override
+ @CallSuper
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ final String childPrefix = innerPrefix + " ";
+ pw.println(prefix + this);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
new file mode 100644
index 0000000..62b9da6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
@@ -0,0 +1,224 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.animation.RectEvaluator;
+import android.animation.TypeEvaluator;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.UnfoldBackgroundController;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Controls transformations of the split screen task surfaces in response
+ * to the unfolding/folding action on foldable devices
+ */
+public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
+
+ private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
+ private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
+
+ private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
+ private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final UnfoldBackgroundController mBackgroundController;
+ private final Executor mExecutor;
+ private final int mExpandedTaskBarHeight;
+ private final float mWindowCornerRadiusPx;
+ private final Rect mStageBounds = new Rect();
+ private final TransactionPool mTransactionPool;
+
+ private InsetsSource mTaskbarInsetsSource;
+ private boolean mBothStagesVisible;
+
+ public StageTaskUnfoldController(@NonNull Context context,
+ @NonNull TransactionPool transactionPool,
+ @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
+ @NonNull DisplayInsetsController displayInsetsController,
+ @NonNull UnfoldBackgroundController backgroundController,
+ @NonNull Executor executor) {
+ mUnfoldProgressProvider = unfoldProgressProvider;
+ mTransactionPool = transactionPool;
+ mExecutor = executor;
+ mBackgroundController = backgroundController;
+ mDisplayInsetsController = displayInsetsController;
+ mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.taskbar_frame_height);
+ }
+
+ /**
+ * Initializes the controller, starts listening for the external events
+ */
+ public void init() {
+ mUnfoldProgressProvider.addListener(mExecutor, this);
+ mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update();
+ }
+ }
+
+ /**
+ * Called when split screen task appeared
+ * @param taskInfo info for the appeared task
+ * @param leash surface leash for the appeared task
+ */
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ AnimationContext context = new AnimationContext(leash);
+ mAnimationContextByTaskId.put(taskInfo.taskId, context);
+ }
+
+ /**
+ * Called when a split screen task vanished
+ * @param taskInfo info for the vanished task
+ */
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
+ if (context != null) {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ resetSurface(transaction, context);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ }
+ mAnimationContextByTaskId.remove(taskInfo.taskId);
+ }
+
+ @Override
+ public void onStateChangeProgress(float progress) {
+ if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
+
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ mBackgroundController.ensureBackground(transaction);
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+
+ context.mCurrentCropRect.set(RECT_EVALUATOR
+ .evaluate(progress, context.mStartCropRect, context.mEndCropRect));
+
+ transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
+ .setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
+ }
+
+ transaction.apply();
+
+ mTransactionPool.release(transaction);
+ }
+
+ @Override
+ public void onStateChangeFinished() {
+ resetTransformations();
+ }
+
+ /**
+ * Called when split screen visibility changes
+ * @param bothStagesVisible true if both stages of the split screen are visible
+ */
+ public void onSplitVisibilityChanged(boolean bothStagesVisible) {
+ mBothStagesVisible = bothStagesVisible;
+ if (!bothStagesVisible) {
+ resetTransformations();
+ }
+ }
+
+ /**
+ * Called when split screen stage bounds changed
+ * @param bounds new bounds for this stage
+ */
+ public void onLayoutChanged(Rect bounds) {
+ mStageBounds.set(bounds);
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update();
+ }
+ }
+
+ private void resetTransformations() {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ resetSurface(transaction, context);
+ }
+ mBackgroundController.removeBackground(transaction);
+ transaction.apply();
+
+ mTransactionPool.release(transaction);
+ }
+
+ private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
+ transaction
+ .setWindowCrop(context.mLeash, null)
+ .setCornerRadius(context.mLeash, 0.0F);
+ }
+
+ private class AnimationContext {
+ final SurfaceControl mLeash;
+ final Rect mStartCropRect = new Rect();
+ final Rect mEndCropRect = new Rect();
+ final Rect mCurrentCropRect = new Rect();
+
+ private AnimationContext(SurfaceControl leash) {
+ this.mLeash = leash;
+ update();
+ }
+
+ private void update() {
+ mStartCropRect.set(mStageBounds);
+
+ if (mTaskbarInsetsSource != null) {
+ // Only insets the cropping window with taskbar when taskbar is expanded
+ if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ mStartCropRect.inset(mTaskbarInsetsSource
+ .calculateVisibleInsets(mStartCropRect));
+ }
+ }
+
+ // Offset to surface coordinates as layout bounds are in screen coordinates
+ mStartCropRect.offsetTo(0, 0);
+
+ mEndCropRect.set(mStartCropRect);
+
+ int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
+ int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
+ mStartCropRect.inset(margin, margin, margin, margin);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index f0685a8..38122ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -38,6 +38,7 @@
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Trace;
+import android.util.Log;
import android.util.PathParser;
import android.window.SplashScreenView;
@@ -50,6 +51,8 @@
*/
public class SplashscreenIconDrawableFactory {
+ private static final String TAG = "SplashscreenIconDrawableFactory";
+
/**
* @return An array containing the foreground drawable at index 0 and if needed a background
* drawable at index 1.
@@ -282,7 +285,12 @@
if (startListener != null) {
startListener.run();
}
- mAnimatableIcon.start();
+ try {
+ mAnimatableIcon.start();
+ } catch (Exception ex) {
+ Log.e(TAG, "Error while running the splash screen animated icon", ex);
+ animation.cancel();
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
index e6d32ff..06df9568 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
@@ -42,6 +42,9 @@
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
<!-- ATM.removeRootTasksWithActivityTypes() -->
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+ <!-- Enable bubble notification-->
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+
<!-- Allow the test to write directly to /sdcard/ -->
<application android:requestLegacyExternalStorage="true">
<uses-library android:name="android.test.runner"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
index c1ec324..9e20bbb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
@@ -20,13 +20,11 @@
import android.content.Context
import android.platform.test.annotations.Presubmit
import android.system.helpers.ActivityHelper
-import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.isRotated
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
@@ -179,15 +177,9 @@
@Presubmit
@Test
- open fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0,
- testSpec.config.endRotation)
- }
+ open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- open fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0,
- testSpec.config.endRotation)
- }
+ open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
new file mode 100644
index 0000000..322d8b5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.wm.shell.flicker.bubble
+
+import android.app.INotificationManager
+import android.app.Instrumentation
+import android.app.NotificationManager
+import android.content.Context
+import android.os.ServiceManager
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
+import com.android.server.wm.flicker.repetitions
+import com.android.wm.shell.flicker.helpers.LaunchBubbleHelper
+import org.junit.Test
+import org.junit.runners.Parameterized
+
+/**
+ * Base configurations for Bubble flicker tests
+ */
+abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) {
+
+ protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ protected val context: Context = instrumentation.context
+ protected val testApp = LaunchBubbleHelper(instrumentation)
+
+ protected val notifyManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE))
+
+ protected val packageManager = context.getPackageManager()
+ protected val uid = packageManager.getApplicationInfo(
+ testApp.component.packageName, 0).uid
+
+ protected lateinit var addBubbleBtn: UiObject2
+ protected lateinit var cancelAllBtn: UiObject2
+
+ protected abstract val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+
+ @JvmOverloads
+ protected open fun buildTransition(
+ extraSpec: FlickerBuilder.(Map<String, Any?>) -> Unit = {}
+ ): FlickerBuilder.(Map<String, Any?>) -> Unit {
+ return { configuration ->
+
+ setup {
+ test {
+ notifyManager.setBubblesAllowed(testApp.component.packageName,
+ uid, NotificationManager.BUBBLE_PREFERENCE_ALL)
+ testApp.launchViaIntent(wmHelper)
+ addBubbleBtn = device.wait(Until.findObject(
+ By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
+ cancelAllBtn = device.wait(Until.findObject(
+ By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
+ }
+ }
+
+ teardown {
+ notifyManager.setBubblesAllowed(testApp.component.packageName,
+ uid, NotificationManager.BUBBLE_PREFERENCE_NONE)
+ testApp.exit()
+ }
+
+ extraSpec(this, configuration)
+ }
+ }
+
+ @FlakyTest
+ @Test
+ fun testAppIsAlwaysVisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ }
+ }
+
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ repeat { testSpec.config.repetitions }
+ transition(this, testSpec.config)
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
+ repetitions = 5)
+ }
+
+ const val FIND_OBJECT_TIMEOUT = 2000L
+ const val SYSTEM_UI_PACKAGE = SYSTEMUI_PACKAGE
+ const val BUBBLE_RES_NAME = "bubble_view"
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
new file mode 100644
index 0000000..bfdcb36
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.wm.shell.flicker.bubble
+
+import android.content.Context
+import android.graphics.Point
+import android.util.DisplayMetrics
+import android.view.WindowManager
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a new activity from bubble.
+ *
+ * To run this test: `atest WMShellFlickerTests:DismissBubbleScreen`
+ *
+ * Actions:
+ * Dismiss a bubble notification
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@Group4
+class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+
+ val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ val displaySize = DisplayMetrics()
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = buildTransition() {
+ setup {
+ eachRun {
+ addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Add Bubble not found")
+ }
+ }
+ transitions {
+ wm?.run { wm.getDefaultDisplay().getMetrics(displaySize) } ?: error("WM not found")
+ val dist = Point((displaySize.widthPixels / 2), displaySize.heightPixels)
+ val showBubble = device.wait(Until.findObject(
+ By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT)
+ showBubble?.run { drag(dist, 1000) } ?: error("Show bubble not found")
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
new file mode 100644
index 0000000..42eeadf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.wm.shell.flicker.bubble
+
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a new activity from bubble.
+ *
+ * To run this test: `atest WMShellFlickerTests:ExpandBubbleScreen`
+ *
+ * Actions:
+ * Launch an app and enable app's bubble notification
+ * Send a bubble notification
+ * The activity for the bubble is launched
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@Group4
+class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = buildTransition() {
+ setup {
+ test {
+ addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Bubble widget not found")
+ }
+ }
+ transitions {
+ val showBubble = device.wait(Until.findObject(
+ By.res("com.android.systemui", "bubble_view")), FIND_OBJECT_TIMEOUT)
+ showBubble?.run { showBubble.click() } ?: error("Bubble notify not found")
+ device.pressBack()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
new file mode 100644
index 0000000..47e8c0c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.wm.shell.flicker.bubble
+
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test creating a bubble notification
+ *
+ * To run this test: `atest WMShellFlickerTests:LaunchBubbleScreen`
+ *
+ * Actions:
+ * Launch an app and enable app's bubble notification
+ * Send a bubble notification
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@Group4
+class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = buildTransition() {
+ transitions {
+ addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Bubble widget not found")
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
new file mode 100644
index 0000000..194e28f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.wm.shell.flicker.bubble
+
+import android.os.SystemClock
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a new activity from bubble.
+ *
+ * To run this test: `atest WMShellFlickerTests:MultiBubblesScreen`
+ *
+ * Actions:
+ * Switch in different bubble notifications
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@Group4
+class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = buildTransition() {
+ setup {
+ test {
+ for (i in 1..3) {
+ addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Add Bubble not found")
+ }
+ val showBubble = device.wait(Until.findObject(
+ By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT)
+ showBubble?.run { showBubble.click() } ?: error("Show bubble not found")
+ SystemClock.sleep(1000)
+ }
+ }
+ transitions {
+ val bubbles = device.wait(Until.findObjects(
+ By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT)
+ for (entry in bubbles) {
+ entry?.run { entry.click() } ?: error("Bubble not found")
+ SystemClock.sleep(1000)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt
new file mode 100644
index 0000000..6695c17
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.helpers
+
+import android.app.Instrumentation
+import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.wm.shell.flicker.testapp.Components
+
+class LaunchBubbleHelper(instrumentation: Instrumentation) : BaseAppHelper(
+ instrumentation,
+ Components.LaunchBubbleActivity.LABEL,
+ Components.LaunchBubbleActivity.COMPONENT.toFlickerComponent()
+) {
+
+ companion object {
+ const val TEST_REPETITIONS = 1
+ const val TIMEOUT_MS = 3_000L
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
index b6680d9..2ccd03b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
@@ -107,13 +107,11 @@
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.endRotation)
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.endRotation)
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index 14b006e..58e1def 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -27,7 +27,6 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
@@ -133,12 +132,10 @@
fun entireScreenCovered() = testSpec.entireScreenCovered()
@Test
- fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.endRotation)
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Test
- fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.endRotation)
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Test
fun topAppLayerIsAlwaysVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
index 8a2b55b..8a50bc0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
@@ -25,7 +25,6 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
@@ -75,15 +74,11 @@
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
index b325157..84676a9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
@@ -25,7 +25,6 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
@@ -74,13 +73,11 @@
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(
- testSpec.config.startRotation, testSpec.config.endRotation)
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(
- testSpec.config.startRotation, testSpec.config.endRotation)
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
index 56933c3..2abdca9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
@@ -24,7 +24,6 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
@@ -83,14 +82,11 @@
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(
- testSpec.config.startRotation, testSpec.config.endRotation)
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
index 5782f14..fe9b9f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
@@ -25,7 +25,6 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
@@ -89,15 +88,11 @@
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@FlakyTest
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 2aa1ed8..c8c3f4d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -111,8 +111,7 @@
*/
@FlakyTest
@Test
- override fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_90, Surface.ROTATION_0)
+ override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
/**
* Checks that the [FlickerComponentName.STATUS_BAR] has the correct position at
@@ -120,8 +119,7 @@
*/
@Presubmit
@Test
- override fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(Surface.ROTATION_90, Surface.ROTATION_0)
+ override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
/**
* Checks that all parts of the screen are covered at the start and end of the transition
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index e3d099f..73626c2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -25,7 +24,6 @@
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -62,14 +60,6 @@
}
}
- @Postsubmit
- @Test
- override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible()
-
- @Postsubmit
- @Test
- override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible()
-
companion object {
/**
* Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 2cdfc2b..9e43dee 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -25,7 +25,6 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import org.junit.FixMethodOrder
import org.junit.Test
@@ -97,8 +96,7 @@
@Presubmit
@Test
- override fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
+ override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index ce840ef..d0fee9a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
@@ -90,7 +89,7 @@
/**
* Checks [pipApp] window remains visible throughout the animation
*/
- @Postsubmit
+ @Presubmit
@Test
fun pipWindowIsAlwaysVisible() {
testSpec.assertWm {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 08d5209..669f37a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -95,18 +95,14 @@
*/
@FlakyTest
@Test
- override fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
/**
* Checks the position of the status bar at the start and end of the transition
*/
@Presubmit
@Test
- override fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
/**
* Checks that [fixedApp] layer is within [screenBoundsStart] at the start of the transition
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index ce89fb6..e8a61e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -177,13 +177,11 @@
@Presubmit
@Test
- open fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
+ open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- open fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
+ open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
index 5549330..2cdbffa 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
@@ -107,5 +107,20 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity
+ android:name=".LaunchBubbleActivity"
+ android:label="LaunchBubbleApp"
+ android:exported="true"
+ android:launchMode="singleTop">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <action android:name="android.intent.action.VIEW" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".BubbleActivity"
+ android:label="BubbleApp"
+ android:exported="false"
+ android:resizeableActivity="true" />
</application>
</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png
new file mode 100644
index 0000000..d424a17
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png
Binary files differ
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml
new file mode 100644
index 0000000..b43f31d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0"/>
+</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml
new file mode 100644
index 0000000..0e8c7a0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,4c-4.97,0 -9,3.58 -9,8c0,1.53 0.49,2.97 1.33,4.18c0.12,0.18 0.2,0.46 0.1,0.66c-0.33,0.68 -0.79,1.52 -1.38,2.39c-0.12,0.17 0.01,0.41 0.21,0.39c0.63,-0.05 1.86,-0.26 3.38,-0.91c0.17,-0.07 0.36,-0.06 0.52,0.03C8.55,19.54 10.21,20 12,20c4.97,0 9,-3.58 9,-8S16.97,4 12,4zM16.94,11.63l-3.29,3.29c-0.13,0.13 -0.34,0.04 -0.34,-0.14v-1.57c0,-0.11 -0.1,-0.21 -0.21,-0.2c-2.19,0.06 -3.65,0.65 -5.14,1.95c-0.15,0.13 -0.38,0 -0.33,-0.19c0.7,-2.57 2.9,-4.57 5.5,-4.75c0.1,-0.01 0.18,-0.09 0.18,-0.19V8.2c0,-0.18 0.22,-0.27 0.34,-0.14l3.29,3.29C17.02,11.43 17.02,11.55 16.94,11.63z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml
new file mode 100644
index 0000000..f8b0ca3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <Button
+ android:id="@+id/button_finish"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_marginStart="8dp"
+ android:text="Finish" />
+ <Button
+ android:id="@+id/button_new_task"
+ android:layout_width="wrap_content"
+ android:layout_height="46dp"
+ android:layout_marginStart="8dp"
+ android:text="New Task" />
+ <Button
+ android:id="@+id/button_new_bubble"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="8dp"
+ android:text="New Bubble" />
+
+ <Button
+ android:id="@+id/button_activity_for_result"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="8dp"
+ android:layout_marginStart="8dp"
+ android:text="Activity For Result" />
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml
new file mode 100644
index 0000000..f23c464
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:color/black">
+
+ <Button
+ android:id="@+id/button_create"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:text="Add Bubble" />
+
+ <Button
+ android:id="@+id/button_cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/button_create"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="20dp"
+ android:text="Cancel Bubble" />
+
+ <Button
+ android:id="@+id/button_cancel_all"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/button_cancel"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="20dp"
+ android:text="Cancel All Bubble" />
+</RelativeLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java
new file mode 100644
index 0000000..bc3bc75
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java
@@ -0,0 +1,77 @@
+/*
+ * 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 com.android.wm.shell.flicker.testapp;
+
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Toast;
+
+public class BubbleActivity extends Activity {
+ private int mNotifId = 0;
+
+ public BubbleActivity() {
+ super();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ if (intent != null) {
+ mNotifId = intent.getIntExtra(BubbleHelper.EXTRA_BUBBLE_NOTIF_ID, -1);
+ } else {
+ mNotifId = -1;
+ }
+
+ setContentView(R.layout.activity_bubble);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ String result = resultCode == Activity.RESULT_OK ? "OK" : "CANCELLED";
+ Toast.makeText(this, "Activity result: " + result, Toast.LENGTH_SHORT).show();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
new file mode 100644
index 0000000..d743dff
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
@@ -0,0 +1,178 @@
+/*
+ * 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.wm.shell.flicker.testapp;
+
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.drawable.Icon;
+import android.os.SystemClock;
+import android.service.notification.StatusBarNotification;
+import android.view.WindowManager;
+
+import java.util.HashMap;
+
+public class BubbleHelper {
+
+ static final String EXTRA_BUBBLE_NOTIF_ID = "EXTRA_BUBBLE_NOTIF_ID";
+ static final String CHANNEL_ID = "bubbles";
+ static final String CHANNEL_NAME = "Bubbles";
+ static final int DEFAULT_HEIGHT_DP = 300;
+
+ private static BubbleHelper sInstance;
+
+ private final Context mContext;
+ private NotificationManager mNotificationManager;
+ private float mDisplayHeight;
+
+ private HashMap<Integer, BubbleInfo> mBubbleMap = new HashMap<>();
+
+ private int mNextNotifyId = 0;
+ private int mColourIndex = 0;
+
+ public static class BubbleInfo {
+ public int id;
+ public int height;
+ public Icon icon;
+
+ public BubbleInfo(int id, int height, Icon icon) {
+ this.id = id;
+ this.height = height;
+ this.icon = icon;
+ }
+ }
+
+ public static BubbleHelper getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new BubbleHelper(context);
+ }
+ return sInstance;
+ }
+
+ private BubbleHelper(Context context) {
+ mContext = context;
+ mNotificationManager = context.getSystemService(NotificationManager.class);
+
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setDescription("Channel that posts bubbles");
+ channel.setAllowBubbles(true);
+ mNotificationManager.createNotificationChannel(channel);
+
+ Point p = new Point();
+ WindowManager wm = context.getSystemService(WindowManager.class);
+ wm.getDefaultDisplay().getRealSize(p);
+ mDisplayHeight = p.y;
+
+ }
+
+ private int getNextNotifyId() {
+ int id = mNextNotifyId;
+ mNextNotifyId++;
+ return id;
+ }
+
+ private Icon getIcon() {
+ return Icon.createWithResource(mContext, R.drawable.bg);
+ }
+
+ public int addNewBubble(boolean autoExpand, boolean suppressNotif) {
+ int id = getNextNotifyId();
+ BubbleInfo info = new BubbleInfo(id, DEFAULT_HEIGHT_DP, getIcon());
+ mBubbleMap.put(info.id, info);
+
+ Notification.BubbleMetadata data = getBubbleBuilder(info)
+ .setSuppressNotification(suppressNotif)
+ .setAutoExpandBubble(false)
+ .build();
+ Notification notification = getNotificationBuilder(info.id)
+ .setBubbleMetadata(data).build();
+
+ mNotificationManager.notify(info.id, notification);
+ return info.id;
+ }
+
+ private Notification.Builder getNotificationBuilder(int id) {
+ Person chatBot = new Person.Builder()
+ .setBot(true)
+ .setName("BubbleBot")
+ .setImportant(true)
+ .build();
+
+ RemoteInput remoteInput = new RemoteInput.Builder("key")
+ .setLabel("Reply")
+ .build();
+
+ String shortcutId = "BubbleChat";
+ return new Notification.Builder(mContext, CHANNEL_ID)
+ .setChannelId(CHANNEL_ID)
+ .setShortcutId(shortcutId)
+ .setContentIntent(PendingIntent.getActivity(mContext, 0,
+ new Intent(mContext, LaunchBubbleActivity.class),
+ PendingIntent.FLAG_UPDATE_CURRENT))
+ .setStyle(new Notification.MessagingStyle(chatBot)
+ .setConversationTitle("Bubble Chat")
+ .addMessage("Hello? This is bubble: " + id,
+ SystemClock.currentThreadTimeMillis() - 300000, chatBot)
+ .addMessage("Is it me, " + id + ", you're looking for?",
+ SystemClock.currentThreadTimeMillis(), chatBot)
+ )
+ .setSmallIcon(R.drawable.ic_bubble);
+ }
+
+ private Notification.BubbleMetadata.Builder getBubbleBuilder(BubbleInfo info) {
+ Intent target = new Intent(mContext, BubbleActivity.class);
+ target.putExtra(EXTRA_BUBBLE_NOTIF_ID, info.id);
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, info.id, target,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ return new Notification.BubbleMetadata.Builder()
+ .setIntent(bubbleIntent)
+ .setIcon(info.icon)
+ .setDesiredHeight(info.height);
+ }
+
+ public void cancel(int id) {
+ mNotificationManager.cancel(id);
+ }
+
+ public void cancelAll() {
+ mNotificationManager.cancelAll();
+ }
+
+ public void cancelLast() {
+ StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications();
+ if (activeNotifications.length > 0) {
+ mNotificationManager.cancel(
+ activeNotifications[activeNotifications.length - 1].getId());
+ }
+ }
+
+ public void cancelFirst() {
+ StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications();
+ if (activeNotifications.length > 0) {
+ mNotificationManager.cancel(activeNotifications[0].getId());
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
index 0ead91b..0ed59bd 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
@@ -87,4 +87,16 @@
public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
PACKAGE_NAME + ".SplitScreenSecondaryActivity");
}
+
+ public static class LaunchBubbleActivity {
+ public static final String LABEL = "LaunchBubbleApp";
+ public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
+ PACKAGE_NAME + ".LaunchBubbleActivity");
+ }
+
+ public static class BubbleActivity {
+ public static final String LABEL = "BubbleApp";
+ public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
+ PACKAGE_NAME + ".BubbleActivity");
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java
new file mode 100644
index 0000000..71fa66d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java
@@ -0,0 +1,82 @@
+/*
+ * 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.wm.shell.flicker.testapp;
+
+
+import android.app.Activity;
+import android.app.Person;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.view.View;
+
+import java.util.Arrays;
+
+public class LaunchBubbleActivity extends Activity {
+
+ private BubbleHelper mBubbleHelper;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addInboxShortcut(getApplicationContext());
+ mBubbleHelper = BubbleHelper.getInstance(this);
+ setContentView(R.layout.activity_main);
+ findViewById(R.id.button_create).setOnClickListener(this::add);
+ findViewById(R.id.button_cancel).setOnClickListener(this::cancel);
+ findViewById(R.id.button_cancel_all).setOnClickListener(this::cancelAll);
+ }
+
+ private void add(View v) {
+ mBubbleHelper.addNewBubble(false /* autoExpand */, false /* suppressNotif */);
+ }
+
+ private void cancel(View v) {
+ mBubbleHelper.cancelLast();
+ }
+
+ private void cancelAll(View v) {
+ mBubbleHelper.cancelAll();
+ }
+
+ private void addInboxShortcut(Context context) {
+ Icon icon = Icon.createWithResource(this, R.drawable.bg);
+ Person[] persons = new Person[4];
+ for (int i = 0; i < persons.length; i++) {
+ persons[i] = new Person.Builder()
+ .setBot(false)
+ .setIcon(icon)
+ .setName("google" + i)
+ .setImportant(true)
+ .build();
+ }
+
+ ShortcutInfo shortcut = new ShortcutInfo.Builder(context, "BubbleChat")
+ .setShortLabel("BubbleChat")
+ .setLongLived(true)
+ .setIntent(new Intent(Intent.ACTION_VIEW))
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_message))
+ .setPersons(persons)
+ .build();
+ ShortcutManager scmanager = context.getSystemService(ShortcutManager.class);
+ scmanager.addDynamicShortcuts(Arrays.asList(shortcut));
+ }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
index 12b547a..2bcc45e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
@@ -62,7 +62,8 @@
@Test
public void testActiveDeactivate() {
- mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct);
+ mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct,
+ true /* reparent */);
assertThat(mMainStage.isActive()).isTrue();
mMainStage.deactivate(mWct);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index be103863..05496b0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -316,7 +316,8 @@
mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class),
mock(Transitions.TransitionFinishCallback.class));
- mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction());
+ mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction(),
+ true /* includingTopTask */);
}
private boolean containsSplitExit(@NonNull WindowContainerTransaction wct) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index a39d331..aeb2849b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -18,9 +18,11 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -111,7 +113,8 @@
mStageCoordinator.moveToSideStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT);
- verify(mMainStage).activate(any(Rect.class), any(WindowContainerTransaction.class));
+ verify(mMainStage).activate(any(Rect.class), any(WindowContainerTransaction.class),
+ eq(true /* includingTopTask */));
verify(mSideStage).addTask(eq(task), any(Rect.class),
any(WindowContainerTransaction.class));
}
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index aa72d965..476a9a58 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1805,9 +1805,15 @@
return false;
}
final int channelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig);
- final int channelCountLimit = AudioFormat.isEncodingLinearFrames(encoding)
- ? AudioSystem.OUT_CHANNEL_COUNT_MAX // PCM limited to OUT_CHANNEL_COUNT_MAX
- : AudioSystem.FCC_24; // Compressed limited to 24 channels
+ final int channelCountLimit;
+ try {
+ channelCountLimit = AudioFormat.isEncodingLinearFrames(encoding)
+ ? AudioSystem.OUT_CHANNEL_COUNT_MAX // PCM limited to OUT_CHANNEL_COUNT_MAX
+ : AudioSystem.FCC_24; // Compressed limited to 24 channels
+ } catch (IllegalArgumentException iae) {
+ loge("Unsupported encoding " + iae);
+ return false;
+ }
if (channelCount > channelCountLimit) {
loge("Channel configuration contains too many channels for encoding "
+ encoding + "(" + channelCount + " > " + channelCountLimit + ")");
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 6d52b66..abd067c 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -38,6 +38,7 @@
import android.media.ISpatializerCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
import android.media.ISpatializerHeadToSoundStagePoseCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.IVolumeController;
import android.media.IVolumeController;
import android.media.PlayerBase;
@@ -440,4 +441,10 @@
void setSpatializerParameter(int key, in byte[] value);
void getSpatializerParameter(int key, inout byte[] value);
+
+ int getSpatializerOutput();
+
+ void registerSpatializerOutputCallback(in ISpatializerOutputCallback cb);
+
+ void unregisterSpatializerOutputCallback(in ISpatializerOutputCallback cb);
}
diff --git a/media/java/android/media/ISpatializerOutputCallback.aidl b/media/java/android/media/ISpatializerOutputCallback.aidl
new file mode 100644
index 0000000..57572a8
--- /dev/null
+++ b/media/java/android/media/ISpatializerOutputCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.media;
+
+/**
+ * AIDL for the AudioService to signal Spatializer output changes.
+ *
+ * {@hide}
+ */
+oneway interface ISpatializerOutputCallback {
+
+ void dispatchSpatializerOutputChanged(int output);
+}
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
index 8b1624b..e6fff39 100644
--- a/media/java/android/media/Spatializer.java
+++ b/media/java/android/media/Spatializer.java
@@ -18,6 +18,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -63,7 +64,9 @@
/**
* Returns whether spatialization is enabled or not.
* A false value can originate for instance from the user electing to
- * disable the feature.<br>
+ * disable the feature, or when the feature is not supported on the device (indicated
+ * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}).
+ * <br>
* Note that this state reflects a platform-wide state of the "desire" to use spatialization,
* but availability of the audio processing is still dictated by the compatibility between
* the effect and the hardware configuration, as indicated by {@link #isAvailable()}.
@@ -85,7 +88,10 @@
* incompatible with sound spatialization, such as playback on a monophonic speaker.<br>
* Note that spatialization can be available, but disabled by the user, in which case this
* method would still return {@code true}, whereas {@link #isEnabled()}
- * would return {@code false}.
+ * would return {@code false}.<br>
+ * Also when the feature is not supported on the device (indicated
+ * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}),
+ * the return value will be false.
* @return {@code true} if the spatializer effect is available and capable
* of processing the audio for the current configuration of the device,
* {@code false} otherwise.
@@ -293,6 +299,24 @@
@HeadTrackingModeSet int mode);
}
+
+ /**
+ * @hide
+ * An interface to be notified of changes to the output stream used by the spatializer
+ * effect.
+ * @see #getOutput()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ public interface OnSpatializerOutputChangedListener {
+ /**
+ * Called when the id of the output stream of the spatializer effect changed.
+ * @param spatializer the {@code Spatializer} instance whose output is updated
+ * @param output the id of the output stream, or 0 when there is no spatializer output
+ */
+ void onSpatializerOutputChanged(@NonNull Spatializer spatializer,
+ @IntRange(from = 0) int output);
+ }
+
/**
* @hide
* An interface to be notified of updates to the head to soundstage pose, as represented by the
@@ -839,6 +863,73 @@
}
}
+ /**
+ * @hide
+ * Returns the id of the output stream used for the spatializer effect playback
+ * @return id of the output stream, or 0 if no spatializer playback is active
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public @IntRange(from = 0) int getOutput() {
+ try {
+ return mAm.getService().getSpatializerOutput();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getSpatializerOutput", e);
+ return 0;
+ }
+ }
+
+ /**
+ * @hide
+ * Sets the listener to receive spatializer effect output updates
+ * @param executor the {@code Executor} handling the callbacks
+ * @param listener the listener to register
+ * @see #clearOnSpatializerOutputChangedListener()
+ * @see #getOutput()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void setOnSpatializerOutputChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnSpatializerOutputChangedListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mOutputListenerLock) {
+ if (mOutputListener != null) {
+ throw new IllegalStateException("Trying to overwrite existing listener");
+ }
+ mOutputListener =
+ new ListenerInfo<OnSpatializerOutputChangedListener>(listener, executor);
+ mOutputDispatcher = new SpatializerOutputDispatcherStub();
+ try {
+ mAm.getService().registerSpatializerOutputCallback(mOutputDispatcher);
+ } catch (RemoteException e) {
+ mOutputListener = null;
+ mOutputDispatcher = null;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Clears the listener for spatializer effect output updates
+ * @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener)
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void clearOnSpatializerOutputChangedListener() {
+ synchronized (mOutputListenerLock) {
+ if (mOutputDispatcher == null) {
+ throw (new IllegalStateException("No listener to clear"));
+ }
+ try {
+ mAm.getService().unregisterSpatializerOutputCallback(mOutputDispatcher);
+ } catch (RemoteException e) { }
+ mOutputListener = null;
+ mOutputDispatcher = null;
+ }
+ }
+
//-----------------------------------------------------------------------------
// callback helper definitions
@@ -964,4 +1055,35 @@
}
}
}
+
+ //-----------------------------------------------------------------------------
+ // output callback management and stub
+ private final Object mOutputListenerLock = new Object();
+ /**
+ * Listener for output updates
+ */
+ @GuardedBy("mOutputListenerLock")
+ private @Nullable ListenerInfo<OnSpatializerOutputChangedListener> mOutputListener;
+ @GuardedBy("mOutputListenerLock")
+ private @Nullable SpatializerOutputDispatcherStub mOutputDispatcher;
+
+ private final class SpatializerOutputDispatcherStub
+ extends ISpatializerOutputCallback.Stub {
+
+ @Override
+ public void dispatchSpatializerOutputChanged(int output) {
+ // make a copy of ref to listener so callback is not executed under lock
+ final ListenerInfo<OnSpatializerOutputChangedListener> listener;
+ synchronized (mOutputListenerLock) {
+ listener = mOutputListener;
+ }
+ if (listener == null) {
+ return;
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ listener.mExecutor.execute(() -> listener.mListener
+ .onSpatializerOutputChanged(Spatializer.this, output));
+ }
+ }
+ }
}
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 127f571..940e5d3 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -164,11 +164,11 @@
<string name="tts_default_lang_title" msgid="4698933575028098940">"ਭਾਸ਼ਾ"</string>
<string name="tts_lang_use_system" msgid="6312945299804012406">"ਸਿਸਟਮ ਭਾਸ਼ਾ ਵਰਤੋ"</string>
<string name="tts_lang_not_selected" msgid="7927823081096056147">"ਭਾਸ਼ਾ ਨਹੀਂ ਚੁਣੀ"</string>
- <string name="tts_default_lang_summary" msgid="9042620014800063470">"ਬੋਲੇ ਗਏ ਟੈਕਸਟ ਲਈ ਭਾਸ਼ਾ-ਵਿਸ਼ੇਸ਼ ਵੌਇਸ ਸੈਟ ਕਰਦਾ ਹੈ"</string>
+ <string name="tts_default_lang_summary" msgid="9042620014800063470">"ਬੋਲੀ ਗਈ ਲਿਖਤ ਲਈ ਭਾਸ਼ਾ-ਵਿਸ਼ੇਸ਼ ਅਵਾਜ਼ ਸੈੱਟ ਕਰਦਾ ਹੈ"</string>
<string name="tts_play_example_title" msgid="1599468547216481684">"ਇੱਕ ਉਦਾਹਰਨ ਲਈ ਸੁਣੋ"</string>
<string name="tts_play_example_summary" msgid="634044730710636383">"ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਦਾ ਇੱਕ ਛੋਟਾ ਪ੍ਰਦਰਸ਼ਨ ਪਲੇ ਕਰੋ"</string>
- <string name="tts_install_data_title" msgid="1829942496472751703">"ਵੌਇਸ ਡਾਟਾ ਸਥਾਪਤ ਕਰੋ"</string>
- <string name="tts_install_data_summary" msgid="3608874324992243851">"ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਲਈ ਲੋੜੀਂਦਾ ਵੌਇਸ ਡਾਟਾ ਸਥਾਪਤ ਕਰੋ"</string>
+ <string name="tts_install_data_title" msgid="1829942496472751703">"ਅਵਾਜ਼ੀ ਡਾਟਾ ਸਥਾਪਤ ਕਰੋ"</string>
+ <string name="tts_install_data_summary" msgid="3608874324992243851">"ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਲਈ ਲੋੜੀਂਦਾ ਅਵਾਜ਼ੀ ਡਾਟਾ ਸਥਾਪਤ ਕਰੋ"</string>
<string name="tts_engine_security_warning" msgid="3372432853837988146">"ਇਹ ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਇੰਜਣ ਉਹ ਸਭ ਲਿਖਤ ਇਕੱਤਰ ਕਰਨ ਵਿੱਚ ਸਮਰੱਥ ਹੋ ਸਕਦਾ ਹੈ, ਜੋ ਬੋਲਿਆ ਜਾਏਗਾ, ਨਿੱਜੀ ਡਾਟਾ ਸਮੇਤ ਜਿਵੇਂ ਪਾਸਵਰਡ ਅਤੇ ਕ੍ਰੈਡਿਟ ਕਾਰਡ ਨੰਬਰ। ਇਹ <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> ਇੰਜਣ ਤੋਂ ਆਉਂਦਾ ਹੈ। ਕੀ ਇਸ ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਇੰਜਣ ਦੀ ਵਰਤੋਂ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਹੈੈ?"</string>
<string name="tts_engine_network_required" msgid="8722087649733906851">"ਇਸ ਭਾਸ਼ਾ ਲਈ ਲਿਖਤ ਤੋਂ ਬੋਲੀ ਆਊਟਪੁੱਟ ਲਈ ਇੱਕ ਚਾਲੂ ਨੈੱਟਵਰਕ ਕਨੈਕਸ਼ਨ ਦੀ ਲੋੜ ਹੈ।"</string>
<string name="tts_default_sample_string" msgid="6388016028292967973">"ਇਹ ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਦਾ ਇੱਕ ਉਦਾਹਰਨ ਹੈ"</string>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 6671308..c6cca5a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -50,6 +50,16 @@
srcs: ["src/com/android/systemui/EventLogTags.logtags"],
}
+filegroup {
+ name: "ReleaseJavaFiles",
+ srcs: ["src/com/android/systemui/flags/FeatureFlagManager.java"],
+}
+
+filegroup {
+ name: "DebugJavaFiles",
+ srcs: ["src-debug/com/android/systemui/flags/FeatureFlagManager.java"],
+}
+
android_library {
name: "SystemUI-core",
srcs: [
@@ -57,6 +67,12 @@
"src/**/*.java",
"src/**/I*.aidl",
],
+ product_variables: {
+ debuggable: {
+ srcs: [":DebugJavaFiles"],
+ exclude_srcs: [":ReleaseJavaFiles"],
+ },
+ },
resource_dirs: [
"res-product",
"res-keyguard",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9de1c5e..58e3d39 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -183,6 +183,9 @@
<permission android:name="com.android.systemui.permission.PLUGIN"
android:protectionLevel="signature" />
+ <permission android:name="com.android.systemui.permission.FLAGS"
+ android:protectionLevel="signature" />
+
<!-- Adding Quick Settings tiles -->
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
index 2765882..a3d924f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
@@ -200,10 +200,10 @@
/**
* Interpolate alpha for notifications background scrim during shade expansion.
* @param fraction Shade expansion fraction
- * @param forNotification If we want the alpha of the notification shade or the scrim.
+ * @param forUiContent If we want the alpha of the scrims, or ui that's on top of them.
*/
- public static float getNotificationScrimAlpha(float fraction, boolean forNotification) {
- if (forNotification) {
+ public static float getNotificationScrimAlpha(float fraction, boolean forUiContent) {
+ if (forUiContent) {
fraction = MathUtils.constrainedMap(0f, 1f, 0.3f, 1f, fraction);
} else {
fraction = MathUtils.constrainedMap(0f, 1f, 0f, 0.5f, fraction);
diff --git a/packages/SystemUI/docs/keyguard.md b/packages/SystemUI/docs/keyguard.md
index e3d48ae..5e7bc1c 100644
--- a/packages/SystemUI/docs/keyguard.md
+++ b/packages/SystemUI/docs/keyguard.md
@@ -8,5 +8,39 @@
Keyguard is the first screen available when turning on the device, as long as the user has not specified a security method of NONE.
+## Critical User Journeys
+
+The journeys below generally refer to Keyguard's portion of the overall flow, especially regarding use of the power button. Power button key interpretation (short press, long press, very long press, multi press) is done in [PhoneWindowManager][4], with calls to [PowerManagerService][2] to sleep or wake up, if needed.
+
+### Power On - AOD enabled or disabled
+
+Begins with the device in low power mode, with the display active for [AOD][3] or inactive. [PowerManagerService][2] can be directed to wake up on various user-configurable signals, such as lift to wake, screen taps, among others. [AOD][2], whether visibly enabled or not, handles these signals to transition AOD to full Lockscreen content. See more in [AOD][3].
+
+### Power Off
+
+An indication to power off the device most likely comes from one of two signals: the user presses the power button or the screen timeout has passed. This may [lock the device](#How-the-device-locks)
+
+#### On Lockscreen
+
+#### On Lockscreen, occluded by an activity
+
+#### Device unlocked, Keyguard has gone away
+
+### Pulsing (Incoming notifications while dozing)
+
+### How the device locks
+
+More coming
+* Screen timeout
+* Smart lock
+* Device policy
+* Power button instantly locks setting
+* Lock timeout after screen timeout setting
+
+
[1]: /frameworks/base/packages/SystemUI/docs/keyguard/bouncer.md
+[2]: /frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
+[3]: /frameworks/base/packages/SystemUI/docs/keyguard/aod.md
+[4]: /frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
+
diff --git a/packages/SystemUI/docs/keyguard/aod.md b/packages/SystemUI/docs/keyguard/aod.md
new file mode 100644
index 0000000..6d76ed55
--- /dev/null
+++ b/packages/SystemUI/docs/keyguard/aod.md
@@ -0,0 +1 @@
+# Always-on Display (AOD)
diff --git a/packages/SystemUI/docs/keyguard/bouncer.md b/packages/SystemUI/docs/keyguard/bouncer.md
index a724966..51f8516 100644
--- a/packages/SystemUI/docs/keyguard/bouncer.md
+++ b/packages/SystemUI/docs/keyguard/bouncer.md
@@ -7,9 +7,9 @@
The bouncer contains a hierarchy of controllers/views to render the user's security method and to manage the authentication attempts.
1. [KeyguardBouncer][1] - Entrypoint for managing the bouncer visibility.
- 1. [KeyguardHostViewController][2] - Intercepts media keys. Can most likely be merged with the next item.
- 1. [KeyguardSecurityContainerController][3] - Manages unlock attempt responses, one-handed use
- 1. [KeyguardSecurityViewFlipperController][4] - Based upon the [KeyguardSecurityModel#SecurityMode][5], will instantiate the required view and controller. PIN, Pattern, etc.
+ 1. [KeyguardHostViewController][2] - Intercepts media keys. Can most likely be merged with the next item.
+ 1. [KeyguardSecurityContainerController][3] - Manages unlock attempt responses, one-handed use
+ 1. [KeyguardSecurityViewFlipperController][4] - Based upon the [KeyguardSecurityModel#SecurityMode][5], will instantiate the required view and controller. PIN, Pattern, etc.
[1]: /frameworks/base/packages/SystemUI/com/android/systemui/statusbar/phone/KeyguardBouncer
[2]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardHostViewController
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index e026012..1844288 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -249,6 +249,9 @@
val population = populationByColor[entry.key]!!
val cam = camByColor[entry.key]!!
val hue = cam.hue.roundToInt() % 360
+ if (cam.chroma <= MIN_CHROMA) {
+ continue
+ }
huePopulation[hue] = huePopulation[hue] + population
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
index d5b9243..17b13a2 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
@@ -16,8 +16,53 @@
package com.android.systemui.flags;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* List of {@link Flag} objects for use in SystemUI.
+ *
+ * Flag Ids are integers. They must be unique.
+ *
+ * On public release builds, flags will always return their default value. There is no way to
+ * change their value on release builds.
*/
public class Flags {
+ public static final BooleanFlag THE_FIRST_FLAG = new BooleanFlag(1, false);
+
+
+ // Pay no attention to the reflection behind the curtain.
+ // ========================== Curtain ==========================
+ // | |
+ // | . . . . . . . . . . . . . . . . . . . |
+ private static Map<Integer, Flag<?>> sFlagMap;
+ static Map<Integer, Flag<?>> collectFlags() {
+ if (sFlagMap != null) {
+ return sFlagMap;
+ }
+ Map<Integer, Flag<?>> flags = new HashMap<>();
+
+ Field[] fields = Flags.class.getFields();
+
+ for (Field field : fields) {
+ Class<?> t = field.getType();
+ if (Flag.class.isAssignableFrom(t)) {
+ try {
+ Flag<?> flag = (Flag<?>) field.get(null);
+ flags.put(flag.getId(), flag);
+ } catch (IllegalAccessException e) {
+ // no-op
+ }
+ }
+ }
+
+ sFlagMap = flags;
+
+ return sFlagMap;
+ }
+ // | . . . . . . . . . . . . . . . . . . . |
+ // | |
+ // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/
+
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
deleted file mode 100644
index ab17499..0000000
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.systemui.plugins;
-
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-
-/**
- * Plugin for loading flag values from an alternate source of truth.
- */
-@ProvidesInterface(action = FlagReaderPlugin.ACTION, version = FlagReaderPlugin.VERSION)
-public interface FlagReaderPlugin extends Plugin {
- int VERSION = 1;
- String ACTION = "com.android.systemui.flags.FLAG_READER_PLUGIN";
-
- /** Returns a boolean value for the given flag. */
- default boolean isEnabled(int id, boolean def) {
- return def;
- }
-
- /** Returns a string value for the given flag id. */
- default String getValue(int id, String def) {
- return def;
- }
-
- /** Returns a int value for the given flag. */
- default int getValue(int id, int def) {
- return def;
- }
-
- /** Returns a long value for the given flag. */
- default long getValue(int id, long def) {
- return def;
- }
-
- /** Returns a float value for the given flag. */
- default float getValue(int id, float def) {
- return def;
- }
-
- /** Returns a double value for the given flag. */
- default double getValue(int id, double def) {
- return def;
- }
-
- /** Add a listener to be alerted when any flag changes. */
- default void addListener(Listener listener) {}
-
- /** Remove a listener to be alerted when any flag changes. */
- default void removeListener(Listener listener) {}
-
- /** A simple listener to be alerted when a flag changes. */
- interface Listener {
- /** */
- void onFlagChanged(int id);
- }
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index a03d849..757dc2e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -52,7 +52,16 @@
boolean isShowingDetail();
void closeDetail();
void animateHeaderSlidingOut();
- void setQsExpansion(float qsExpansionFraction, float headerTranslation);
+
+ /**
+ * Asks QS to update its presentation, according to {@code NotificationPanelViewController}.
+ *
+ * @param qsExpansionFraction How much each UI element in QS should be expanded (QQS to QS.)
+ * @param panelExpansionFraction Whats the expansion of the whole shade.
+ * @param headerTranslation How much we should vertically translate QS.
+ */
+ void setQsExpansion(float qsExpansionFraction, float panelExpansionFraction,
+ float headerTranslation);
void setHeaderListening(boolean listening);
void notifyCustomizeChanged();
void setContainerController(QSContainerController controller);
@@ -75,13 +84,13 @@
/**
* If QS should translate as we pull it down, or if it should be static.
*/
- void setTranslateWhileExpanding(boolean shouldTranslate);
+ void setInSplitShade(boolean shouldTranslate);
/**
* Set the amount of pixels we have currently dragged down if we're transitioning to the full
* shade. 0.0f means we're not transitioning yet.
*/
- default void setTransitionToFullShadeAmount(float pxAmount, boolean animated) {}
+ default void setTransitionToFullShadeAmount(float pxAmount, float progress) {}
/**
* A rounded corner clipping that makes QS feel as if it were behind everything.
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 28c6166..87a9825 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -51,7 +51,7 @@
android:id="@+id/lockscreen_clock_view_large"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_below="@id/keyguard_status_area"
+ android:layout_below="@id/keyguard_slice_view"
android:visibility="gone">
<com.android.keyguard.AnimatableClockView
android:id="@+id/animatable_clock_view_large"
@@ -68,19 +68,28 @@
lockScreenWeight="400"
/>
</FrameLayout>
- <include layout="@layout/keyguard_status_area"
+
+ <!-- Not quite optimal but needed to translate these items as a group. The
+ NotificationIconContainer has its own logic for translation. -->
+ <LinearLayout
android:id="@+id/keyguard_status_area"
+ android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
- android:layout_below="@id/lockscreen_clock_view" />
+ android:layout_below="@id/lockscreen_clock_view">
- <com.android.systemui.statusbar.phone.NotificationIconContainer
- android:id="@+id/left_aligned_notification_icon_container"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_shelf_height"
- android:layout_below="@id/keyguard_status_area"
- android:paddingStart="@dimen/below_clock_padding_start_icons"
- android:visibility="invisible"
- />
+ <include layout="@layout/keyguard_slice_view"
+ android:id="@+id/keyguard_slice_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <com.android.systemui.statusbar.phone.NotificationIconContainer
+ android:id="@+id/left_aligned_notification_icon_container"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_shelf_height"
+ android:paddingStart="@dimen/below_clock_padding_start_icons"
+ android:visibility="invisible"
+ />
+ </LinearLayout>
</com.android.keyguard.KeyguardClockSwitch>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml
similarity index 89%
rename from packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
rename to packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml
index 95eb5c1..1863d11 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml
@@ -22,11 +22,10 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
+ android:layout_gravity="start"
android:clipToPadding="false"
android:orientation="vertical"
- android:paddingStart="@dimen/below_clock_padding_start"
- android:layout_centerHorizontal="true">
+ android:paddingStart="@dimen/below_clock_padding_start">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
@@ -42,6 +41,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:gravity="center"
+ android:gravity="start"
/>
</com.android.keyguard.KeyguardSliceView>
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml
new file mode 100644
index 0000000..363a022
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <stroke
+ android:color="?androidprv:attr/colorAccentPrimaryVariant"
+ android:width="1dp"/>
+ <corners android:radius="20dp"/>
+ <padding
+ android:left="16dp"
+ android:right="16dp"
+ android:top="8dp"
+ android:bottom="8dp" />
+ <solid android:color="@android:color/transparent" />
+</shape>
diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index b338894..cd6bc11 100644
--- a/packages/SystemUI/res/layout/media_output_dialog.xml
+++ b/packages/SystemUI/res/layout/media_output_dialog.xml
@@ -24,41 +24,44 @@
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="94dp"
+ android:layout_height="96dp"
android:gravity="start|center_vertical"
android:paddingStart="16dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/header_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingEnd="@dimen/media_output_dialog_header_icon_padding"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
android:importantForAccessibility="no"/>
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="16dp"
+ android:layout_height="match_parent"
+ android:paddingStart="16dp"
+ android:paddingTop="20dp"
+ android:paddingBottom="24dp"
+ android:paddingEnd="24dp"
android:orientation="vertical">
<TextView
android:id="@+id/header_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
+ android:gravity="center_vertical"
android:maxLines="1"
android:textColor="?android:attr/textColorPrimary"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
android:textSize="20sp"/>
-
<TextView
android:id="@+id/header_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:gravity="center_vertical"
android:ellipsize="end"
android:maxLines="1"
+ android:textColor="?android:attr/textColorTertiary"
android:fontFamily="roboto-regular"
- android:textSize="14sp"/>
-
+ android:textSize="16sp"/>
</LinearLayout>
</LinearLayout>
@@ -81,21 +84,21 @@
android:overScrollMode="never"/>
</LinearLayout>
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?android:attr/listDivider"/>
-
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:layout_marginStart="24dp"
+ android:layout_marginBottom="18dp"
+ android:layout_marginEnd="24dp"
android:orientation="horizontal">
<Button
android:id="@+id/stop"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ style="@style/MediaOutputRoundedOutlinedButton"
android:layout_width="wrap_content"
- android:layout_height="64dp"
+ android:layout_height="36dp"
+ android:minWidth="0dp"
android:text="@string/keyboard_key_media_stop"
android:visibility="gone"/>
@@ -106,10 +109,10 @@
<Button
android:id="@+id/done"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ style="@style/MediaOutputRoundedOutlinedButton"
android:layout_width="wrap_content"
- android:layout_height="64dp"
- android:layout_marginEnd="0dp"
+ android:layout_height="36dp"
+ android:minWidth="0dp"
android:text="@string/inline_done_button"/>
</LinearLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml
index 16c03e1..a5a7efa 100644
--- a/packages/SystemUI/res/layout/media_output_list_item.xml
+++ b/packages/SystemUI/res/layout/media_output_list_item.xml
@@ -23,17 +23,20 @@
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
- android:layout_height="64dp">
+ android:layout_height="88dp"
+ android:paddingTop="24dp"
+ android:paddingBottom="16dp"
+ android:paddingStart="24dp"
+ android:paddingEnd="8dp">
<FrameLayout
- android:layout_width="36dp"
- android:layout_height="36dp"
- android:layout_gravity="center_vertical|start"
- android:layout_marginStart="16dp">
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical|start">
<ImageView
android:id="@+id/title_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
android:layout_gravity="center"/>
</FrameLayout>
@@ -42,49 +45,69 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
- android:layout_marginStart="68dp"
+ android:layout_marginStart="64dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:attr/textColorPrimary"
- android:textSize="14sp"/>
+ android:textSize="16sp"/>
<RelativeLayout
android:id="@+id/two_line_layout"
android:layout_width="wrap_content"
android:layout_height="48dp"
- android:layout_marginStart="52dp"
- android:layout_marginEnd="69dp"
- android:layout_marginTop="10dp">
+ android:layout_marginStart="48dp">
<TextView
android:id="@+id/two_line_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:layout_marginEnd="15dp"
+ android:layout_marginEnd="48dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:attr/textColorPrimary"
- android:textSize="14sp"/>
+ android:textSize="16sp"/>
<TextView
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="15dp"
- android:layout_marginBottom="7dp"
+ android:layout_marginTop="4dp"
android:layout_alignParentBottom="true"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:attr/textColorSecondary"
- android:textSize="12sp"
+ android:textSize="14sp"
android:fontFamily="roboto-regular"
android:visibility="gone"/>
<SeekBar
android:id="@+id/volume_seekbar"
+ android:layout_marginTop="16dp"
+ android:layout_marginEnd="8dp"
style="@*android:style/Widget.DeviceDefault.SeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"/>
+ <ImageView
+ android:id="@+id/add_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="right"
+ android:layout_marginEnd="24dp"
+ android:layout_alignParentRight="true"
+ android:src="@drawable/ic_add"
+ android:tint="?android:attr/colorAccent"
+ />
+ <CheckBox
+ android:id="@+id/check_box"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="right"
+ android:layout_marginEnd="24dp"
+ android:layout_alignParentRight="true"
+ android:button="@drawable/ic_check_box"
+ android:visibility="gone"
+ />
</RelativeLayout>
<ProgressBar
@@ -92,47 +115,17 @@
style="@*android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="258dp"
android:layout_height="18dp"
- android:layout_marginStart="68dp"
- android:layout_marginTop="40dp"
+ android:layout_marginStart="64dp"
+ android:layout_marginTop="28dp"
android:indeterminate="true"
android:indeterminateOnly="true"
android:visibility="gone"/>
-
- <View
- android:id="@+id/end_divider"
- android:layout_width="1dp"
- android:layout_height="36dp"
- android:layout_marginEnd="68dp"
- android:layout_gravity="right|center_vertical"
- android:background="?android:attr/listDivider"
- android:visibility="gone"/>
-
- <ImageView
- android:id="@+id/add_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_gravity="right|center_vertical"
- android:layout_marginEnd="24dp"
- android:src="@drawable/ic_add"
- android:tint="?android:attr/colorAccent"
- android:visibility="gone"/>
-
- <CheckBox
- android:id="@+id/check_box"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_gravity="right|center_vertical"
- android:layout_marginEnd="24dp"
- android:button="@drawable/ic_check_box"
- android:visibility="gone"/>
</FrameLayout>
<View
android:id="@+id/bottom_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="12dp"
android:layout_gravity="bottom"
android:background="?android:attr/listDivider"
android:visibility="gone"/>
diff --git a/packages/SystemUI/res/layout/qs_user_dialog_content.xml b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
index 321fe68..543b7d7 100644
--- a/packages/SystemUI/res/layout/qs_user_dialog_content.xml
+++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
@@ -16,74 +16,78 @@
~ limitations under the License.
-->
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="24dp"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
- android:background="@drawable/qs_dialog_bg"
->
- <TextView
- android:id="@+id/title"
+ android:layout_height="wrap_content">
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_width="0dp"
- android:textAlignment="center"
- android:text="@string/qs_user_switch_dialog_title"
- android:textAppearance="@style/TextAppearance.QSDialog.Title"
- android:layout_marginBottom="32dp"
- sysui:layout_constraintTop_toTopOf="parent"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toEndOf="parent"
- sysui:layout_constraintBottom_toTopOf="@id/grid"
+ android:padding="24dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ >
+ <TextView
+ android:id="@+id/title"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:textAlignment="center"
+ android:text="@string/qs_user_switch_dialog_title"
+ android:textAppearance="@style/TextAppearance.QSDialog.Title"
+ android:layout_marginBottom="32dp"
+ sysui:layout_constraintTop_toTopOf="parent"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ sysui:layout_constraintBottom_toTopOf="@id/grid"
+ />
+
+ <com.android.systemui.qs.PseudoGridView
+ android:id="@+id/grid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="28dp"
+ sysui:verticalSpacing="4dp"
+ sysui:horizontalSpacing="4dp"
+ sysui:fixedChildWidth="80dp"
+ sysui:layout_constraintTop_toBottomOf="@id/title"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ sysui:layout_constraintBottom_toTopOf="@id/barrier"
/>
- <com.android.systemui.qs.PseudoGridView
- android:id="@+id/grid"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="28dp"
- sysui:verticalSpacing="4dp"
- sysui:horizontalSpacing="4dp"
- sysui:fixedChildWidth="80dp"
- sysui:layout_constraintTop_toBottomOf="@id/title"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toEndOf="parent"
- sysui:layout_constraintBottom_toTopOf="@id/barrier"
- />
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/barrier"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ sysui:barrierDirection="top"
+ sysui:constraint_referenced_ids="settings,done"
+ />
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/barrier"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- sysui:barrierDirection="top"
- sysui:constraint_referenced_ids="settings,done"
- />
+ <Button
+ android:id="@+id/settings"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="@string/quick_settings_more_user_settings"
+ sysui:layout_constraintTop_toBottomOf="@id/barrier"
+ sysui:layout_constraintBottom_toBottomOf="parent"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toStartOf="@id/done"
+ sysui:layout_constraintHorizontal_chainStyle="spread_inside"
+ style="@style/Widget.QSDialog.Button.BorderButton"
+ />
- <Button
- android:id="@+id/settings"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:text="@string/quick_settings_more_user_settings"
- sysui:layout_constraintTop_toBottomOf="@id/barrier"
- sysui:layout_constraintBottom_toBottomOf="parent"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toStartOf="@id/done"
- sysui:layout_constraintHorizontal_chainStyle="spread_inside"
- style="@style/Widget.QSDialog.Button.BorderButton"
- />
+ <Button
+ android:id="@+id/done"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="@string/quick_settings_done"
+ sysui:layout_constraintTop_toBottomOf="@id/barrier"
+ sysui:layout_constraintBottom_toBottomOf="parent"
+ sysui:layout_constraintStart_toEndOf="@id/settings"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ style="@style/Widget.QSDialog.Button"
+ />
- <Button
- android:id="@+id/done"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:text="@string/quick_settings_done"
- sysui:layout_constraintTop_toBottomOf="@id/barrier"
- sysui:layout_constraintBottom_toBottomOf="parent"
- sysui:layout_constraintStart_toEndOf="@id/settings"
- sysui:layout_constraintEnd_toEndOf="parent"
- style="@style/Widget.QSDialog.Button"
- />
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 18315f1..cc1af87 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -65,18 +65,6 @@
android:layout_gravity="center"
android:scaleType="centerCrop"/>
- <!-- Fingerprint -->
- <!-- AOD dashed fingerprint icon with moving dashes -->
- <com.airbnb.lottie.LottieAnimationView
- android:id="@+id/lock_udfps_aod_fp"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="@dimen/lock_icon_padding"
- android:layout_gravity="center"
- android:scaleType="centerCrop"
- systemui:lottie_autoPlay="false"
- systemui:lottie_loop="true"
- systemui:lottie_rawRes="@raw/udfps_aod_fp"/>
</com.android.keyguard.LockIconView>
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
diff --git a/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml b/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml
new file mode 100644
index 0000000..f5bfa49
--- /dev/null
+++ b/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ 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.
+ -->
+<com.airbnb.lottie.LottieAnimationView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/lock_udfps_aod_fp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/lock_icon_padding"
+ android:layout_gravity="center"
+ android:scaleType="centerCrop"
+ systemui:lottie_autoPlay="false"
+ systemui:lottie_loop="true"
+ systemui:lottie_rawRes="@raw/udfps_aod_fp"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index f589498..522fcb2 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-fi sal vir nou nie outomaties koppel nie"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Sien alles"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ontkoppel Ethernet om netwerke te wissel"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kies gebruiker"</string>
</resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index e94dc68..432c557 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wifi ለአሁን በራስ-ሰር አይገናኝም"</string>
<string name="see_all_networks" msgid="3773666844913168122">"ሁሉንም ይመልከቱ"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"አውታረ መረቦችን ለመቀየር፣ የኢተርኔት ግንኙነት ያቋርጡ"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ተጠቃሚን ይምረጡ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 14753da..2728c98 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -1202,4 +1202,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"لن يتم الاتصال بشبكة Wi-Fi تلقائيًا في الوقت الحالي."</string>
<string name="see_all_networks" msgid="3773666844913168122">"عرض الكل"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"للتبديل بين الشبكات، يجب فصل إيثرنت."</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"اختيار المستخدم"</string>
</resources>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index a50d8c1..e8158f1 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"এতিয়া ৱাই-ফাই স্বয়ংক্ৰিয়ভাৱে সংযুক্ত নহ’ব"</string>
<string name="see_all_networks" msgid="3773666844913168122">"আটাইবোৰ চাওক"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"নেটৱৰ্ক সলনি কৰিবলৈ ইথাৰনেটৰ পৰা সংযোগ বিচ্ছিন্ন কৰক"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ব্যৱহাৰকাৰী বাছনি কৰক"</string>
</resources>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index e0ef1ec..0203288 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi hələlik avtomatik qoşulmayacaq"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Hamısına baxın"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Şəbəkəni dəyişmək üçün etherneti ayırın"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"İstifadəçi seçin"</string>
</resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 31f8014..60dee9e 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -1184,4 +1184,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"WiFi trenutno ne može da se automatski poveže"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Pogledajte sve"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Da biste promenili mrežu, prekinite eternet vezu"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Izaberite korisnika"</string>
</resources>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index b296aa6..f234be4 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -1190,4 +1190,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Аўтаматычнае падключэнне да Wi-Fi адсутнічае"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Паказаць усе"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Каб падключыцца да сетак, выключыце Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Выбар карыстальніка"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 84c2291..170a565 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Засега Wi-Fi няма да се свързва автоматично"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Вижте всички"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"За да превключите мрежите, прекъснете връзката с Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Избор на потребител"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 57e70a6..f4b1935 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"এখন ওয়াই-ফাই নিজে থেকে কানেক্ট হবে না"</string>
<string name="see_all_networks" msgid="3773666844913168122">"সবকটি দেখুন"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"নেটওয়ার্ক বদলাতে ইথারনেট ডিসকানেক্ট করুন"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ব্যবহারকারী বেছে নিন"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index c8588ec..4f5db01 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -1184,4 +1184,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"WiFi se trenutno ne može automatski povezati"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Prikaži sve"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Da promijenite mrežu, isključite ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Odaberite korisnika"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index cfe70c2..ace4791 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Per ara la Wi‑Fi no es connectarà automàticament"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Mostra-ho tot"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Per canviar de xarxa, desconnecta la connexió Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecciona un usuari"</string>
</resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 3d648cb..f4c055d 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -1190,4 +1190,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi se prozatím nebude připojovat automaticky"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Zobrazit vše"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Pokud chcete přepnout sítě, odpojte ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Zvolte uživatele"</string>
</resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index eb715aa..3a6030e 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Ingen automatisk forbindelse til Wi-Fi i øjeblikket"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Se alle"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Afbryd ethernetforbindelsen for at skifte netværk"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Vælg bruger"</string>
</resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index b815492..be501ca 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Zurzeit wird keine automatische WLAN-Verbindung hergestellt"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Alle ansehen"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Trenne das Ethernetkabel, um das Netzwerk zu wechseln"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Nutzer auswählen"</string>
</resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index c6cc7f0..145c72d 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Δεν θα γίνεται προς το παρόν αυτόματη σύνδεση Wi-Fi."</string>
<string name="see_all_networks" msgid="3773666844913168122">"Εμφάνιση όλων"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Για εναλλαγή δικτύων, αποσυνδέστε το ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Επιλογή χρήστη"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index e1006fc..590033a 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi‑Fi won’t auto-connect for now"</string>
<string name="see_all_networks" msgid="3773666844913168122">"See all"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 66b44d5..ca7099f 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi‑Fi won’t auto-connect for now"</string>
<string name="see_all_networks" msgid="3773666844913168122">"See all"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index e1006fc..590033a 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi‑Fi won’t auto-connect for now"</string>
<string name="see_all_networks" msgid="3773666844913168122">"See all"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index e1006fc..590033a 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi‑Fi won’t auto-connect for now"</string>
<string name="see_all_networks" msgid="3773666844913168122">"See all"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 9af7322..1a26024 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi‑Fi won’t auto-connect for now"</string>
<string name="see_all_networks" msgid="3773666844913168122">"See all"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 8d53bb9..bfe22c2 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Por ahora, el Wi-Fi no se conectará automáticamente"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Ver todo"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para cambiar de red, desconéctate de Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 8db8770..762c1be 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Por ahora no se conectará automáticamente a redes Wi-Fi"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Ver todo"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para cambiar de red, desconecta el cable Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string>
</resources>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index c3cb552..04874d2 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"WiFi-ühendust ei looda praegu automaatselt"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Kuva kõik"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Võrkude vahetamiseks katkestage Etherneti-ühendus"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kasutaja valimine"</string>
</resources>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 8df329a..5c6fc3b 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Oraingoz ez da automatikoki konektatuko wifira"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Ikusi guztiak"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Sarea aldatzeko, deskonektatu Ethernet-a"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Hautatu erabiltzaile bat"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index ae308b7..ae8fab6 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"فعلاً Wi-Fi بهطور خودکار متصل نمیشود"</string>
<string name="see_all_networks" msgid="3773666844913168122">"مشاهده همه"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"برای تغییر شبکه، اترنت را قطع کنید"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"انتخاب کاربر"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index ee0cdb6..9bd16bd 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi ei toistaiseksi yhdistä automaattisesti"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Näytä kaikki"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Irrota Ethernet-johto, jos haluat vaihtaa verkkoa"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Valitse käyttäjä"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 7fcbcfb..d8b312f 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Connexion automatique au Wi-Fi impossible pour le moment"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Tout afficher"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Pour changer de réseau, débranchez le câble Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Sélect. utilisateur"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 74e55fc..e897a69 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Connexion automatique au Wi-Fi désactivée pour le moment"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Tout afficher"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Pour changer de réseau, déconnectez l\'Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Choisir utilisateur"</string>
</resources>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 3bcfcc8..328ac9c 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"De momento, a wifi non se conectará automaticamente"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Ver todo"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para cambiar de rede, desconecta a Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string>
</resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index e713cb7..3524d85 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"હમણાં પૂરતું વાઇ-ફાઇ ઑટોમૅટિક રીતે કનેક્ટ થશે નહીં"</string>
<string name="see_all_networks" msgid="3773666844913168122">"બધા જુઓ"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"બીજા નેટવર્ક પર જવા માટે, ઇથરનેટ ડિસ્કનેક્ટ કરો"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"વપરાશકર્તા પસંદ કરો"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index edd7fc6..3aec1f4 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"फ़िलहाल, वाई-फ़ाई अपने-आप कनेक्ट नहीं होगा"</string>
<string name="see_all_networks" msgid="3773666844913168122">"सभी देखें"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"नेटवर्क बदलने के लिए, पहले ईथरनेट को डिसकनेक्ट करें"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"उपयोगकर्ता चुनें"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index b066191..0b89903 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -1184,4 +1184,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi se zasad neće automatski povezivati"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Prikaži sve"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Da biste se prebacili na drugu mrežu, odspojite Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Odabir korisnika"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 5880cf0..573fa6e 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"A Wi-Fi-re történő csatlakozás jelenleg nem automatikus"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Megtekintés"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Hálózatváltáshoz válassza le az ethernetet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Felhasználóválasztás"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index ad3cf25..c6be8c0 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi-ն ավտոմատ չի միանա"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Տեսնել բոլորը"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Մի ցանցից մյուսին անցնելու համար անջատեք Ethernet-ը"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Ընտրեք օգտատեր"</string>
</resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index c42b143..53c7e26 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi tidak akan otomatis terhubung untuk saat ini"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Lihat semua"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Untuk beralih jaringan, lepaskan kabel ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pilih pengguna"</string>
</resources>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 4692280..76953ef 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi tengist ekki sjálfkrafa eins og er"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Sjá allt"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Aftengdu ethernet til að skipta um net"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Velja notanda"</string>
</resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 1d96598..d2467b1 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Connessione automatica rete Wi-Fi non attiva al momento"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Mostra tutte"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Per cambiare rete, scollega il cavo Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleziona utente"</string>
</resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 8fd2bb9..b4566e8 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -1190,4 +1190,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"ה-Wi-Fi לא יתחבר באופן אוטומטי בינתיים"</string>
<string name="see_all_networks" msgid="3773666844913168122">"הצגת הכול"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"כדי לעבור בין רשתות, צריך לנתק את האתרנט"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"בחירת משתמש"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 3134f73..34f9c0c 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi に自動接続しません"</string>
<string name="see_all_networks" msgid="3773666844913168122">"すべて表示"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ネットワークを変更するにはイーサネット接続を解除してください"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ユーザーの選択"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index c4c73c5..bead5fe 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi ინტერნეტს დროებით ავტომატურად არ დაუკავშირდება"</string>
<string name="see_all_networks" msgid="3773666844913168122">"ყველას ნახვა"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ქსელების გადასართავად, გაწყვიტეთ Ethernet-თან კავშირი"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"მომხმარებლის არჩევა"</string>
</resources>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index bccc0ab..14e8456 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Әзірше Wi-Fi автоматты түрде қосылмайды."</string>
<string name="see_all_networks" msgid="3773666844913168122">"Барлығын көру"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Желілерді ауыстыру үшін ethernet кабелін ажыратыңыз."</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Пайдаланушыны таңдау"</string>
</resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 7d79023..9bcc41a 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi នឹងមិនភ្ជាប់ដោយស្វ័យប្រវត្តិក្នុងពេលនេះទេ"</string>
<string name="see_all_networks" msgid="3773666844913168122">"មើលទាំងអស់"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ដើម្បីប្ដូរបណ្ដាញ សូមផ្ដាច់អ៊ីសឺរណិត"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ជ្រើសរើសអ្នកប្រើប្រាស់"</string>
</resources>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index ca69a82..cad8abb 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"ಸದ್ಯದ ಮಟ್ಟಿಗೆ ವೈ-ಫೈ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಕನೆಕ್ಟ್ ಆಗುವುದಿಲ್ಲ"</string>
<string name="see_all_networks" msgid="3773666844913168122">"ಎಲ್ಲವನ್ನೂ ನೋಡಿ"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ನೆಟ್ವರ್ಕ್ಗಳನ್ನು ಬದಲಿಸಲು, ಇಥರ್ನೆಟ್ ಅನ್ನು ಡಿಸ್ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ಬಳಕೆದಾರ ಆಯ್ಕೆಮಾಡಿ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 3a8657b..6541cbf 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"지금은 Wi-Fi가 자동으로 연결되지 않습니다."</string>
<string name="see_all_networks" msgid="3773666844913168122">"모두 보기"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"네트워크를 전환하려면 이더넷을 연결 해제하세요."</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"사용자 선택"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 9f242f34..1c6f3be 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi азырынча автоматтык түрдө туташпайт"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Баарын көрүү"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Башка тармактарга которулуу үчүн Ethernet кабелин ажыратыңыз"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Колдонуучуну тандоо"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index afd914d..dfe5916 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi ຈະບໍ່ເຊື່ອມຕໍ່ອັດຕະໂນມັດສຳລັບຕອນນີ້"</string>
<string name="see_all_networks" msgid="3773666844913168122">"ເບິ່ງທັງໝົດ"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ເພື່ອສະຫຼັບເຄືອຂ່າຍ, ໃຫ້ຕັດການເຊື່ອມຕໍ່ອີເທີເນັດກ່ອນ"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ເລືອກຜູ້ໃຊ້"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 233610f..57af386 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -1190,4 +1190,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"„Wi-Fi“ šiuo metu nebus prijungtas automatiškai"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Žiūrėti viską"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Norėdami perjungti tinklus, atjunkite eternetą"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Naudotojo pasirinkimas"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 83e6d6e..ced02b3 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -1184,4 +1184,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi savienojums īslaicīgi netiks izveidots automātiski."</string>
<string name="see_all_networks" msgid="3773666844913168122">"Visu tīklu skatīšana"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Lai pārslēgtu tīklus, atvienojiet tīkla Ethernet vadu."</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Lietotāja atlase"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index e00108a..7b54b63 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi нема да се поврзува автоматски засега"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Прикажи ги сите"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"За промена на мрежата, прекинете ја врската со етернетот"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Изберете корисник"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 49beea2..6e924aa 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"വൈഫൈ ഇപ്പോൾ സ്വയമേവ കണക്റ്റ് ചെയ്യില്ല"</string>
<string name="see_all_networks" msgid="3773666844913168122">"എല്ലാം കാണുക"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"മറ്റ് നെറ്റ്വർക്കുകളിലേക്ക് മാറാൻ, ഇതർനെറ്റ് വിച്ഛേദിക്കുക"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ഉപയോക്താവിനെ തിരഞ്ഞെടുക്കൂ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index f6dea18..5512659 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi-г одоогоор автоматаар холбохгүй"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Бүгдийг харах"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Сүлжээг сэлгэхийн тулд этернэтийг салгана уу"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Хэрэглэгч сонгох"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 6156cf5..2864f32 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -848,7 +848,7 @@
<string name="keyboard_shortcut_group_applications_sms" msgid="6912633831752843566">"SMS"</string>
<string name="keyboard_shortcut_group_applications_music" msgid="9032078456666204025">"संगीत"</string>
<string name="keyboard_shortcut_group_applications_youtube" msgid="5078136084632450333">"YouTube"</string>
- <string name="keyboard_shortcut_group_applications_calendar" msgid="4229602992120154157">"Calendar"</string>
+ <string name="keyboard_shortcut_group_applications_calendar" msgid="4229602992120154157">"कॅलेंडर"</string>
<string name="tuner_full_zen_title" msgid="5120366354224404511">"आवाज नियंत्रणांसह दर्शवा"</string>
<string name="volume_and_do_not_disturb" msgid="502044092739382832">"व्यत्यय आणू नका"</string>
<string name="volume_dnd_silent" msgid="4154597281458298093">"आवाजाच्या बटणांचा शार्टकट"</string>
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"सध्या वाय-फाय ऑटो-कनेक्ट होणार नाही"</string>
<string name="see_all_networks" msgid="3773666844913168122">"सर्व पहा"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"नेटवर्क स्विच करण्यासाठी, इथरनेट केबल डिस्कनेक्ट करा"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"वापरकर्ता निवडा"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 9dde6b6..96c7f89 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi tidak akan disambungkan secara automatik buat masa ini"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Lihat semua"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Untuk menukar rangkaian, putuskan sambungan ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pilih pengguna"</string>
</resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 666f9ed..1d521dc 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi က လောလောဆယ် အလိုအလျောက် ချိတ်ဆက်မည်မဟုတ်ပါ"</string>
<string name="see_all_networks" msgid="3773666844913168122">"အားလုံးကြည့်ရန်"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ကွန်ရက်ပြောင်းရန် အီသာနက်ကို ချိတ်ဆက်မှုဖြုတ်ပါ"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"အသုံးပြုသူ ရွေးခြင်း"</string>
</resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index c25bec7..32a3740 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi kobles ikke til automatisk inntil videre"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Se alle"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"For å bytte nettverk, koble fra Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Velg bruker"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index ce1787c..8b50929b 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"केही समयका लागि Wi-Fi स्वतः कनेक्ट हुँदैन"</string>
<string name="see_all_networks" msgid="3773666844913168122">"सबै नेटवर्क हेर्नुहोस्"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"नेटवर्क बदल्न इथरनेट डिस्कनेक्ट गर्नुहोस्"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"प्रयोगकर्ता चयन गर्नु…"</string>
</resources>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index ffcc3a8..07e28b6 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -16,7 +16,9 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog" />
+ <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog">
+ <item name="android:buttonCornerRadius">28dp</item>
+ </style>
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert" />
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index ad1403e..2c9b459 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wifi maakt momenteel niet automatisch verbinding"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Alles tonen"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Verbreek de ethernetverbinding om van netwerk te wisselen"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Gebruiker selecteren"</string>
</resources>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index e5c2a2b..a69c9a9 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"ବର୍ତ୍ତମାନ ପାଇଁ ୱାଇ-ଫାଇ ସ୍ୱତଃ-ସଂଯୋଗ ହେବ ନାହିଁ"</string>
<string name="see_all_networks" msgid="3773666844913168122">"ସବୁ ଦେଖନ୍ତୁ"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ନେଟୱାର୍କ ସ୍ୱିଚ୍ କରିବାକୁ, ଇଥରନେଟ୍ ବିଚ୍ଛିନ୍ନ କରନ୍ତୁ"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ଉପଯୋଗକର୍ତ୍ତା ଚୟନ କର"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 48750cf..7288263 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"ਫ਼ਿਲਹਾਲ ਵਾਈ-ਫਾਈ ਸਵੈ-ਕਨੈਕਟ ਨਹੀਂ ਹੋਵੇਗਾ"</string>
<string name="see_all_networks" msgid="3773666844913168122">"ਸਭ ਦੇਖੋ"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ਨੈੱਟਵਰਕਾਂ ਨੂੰ ਬਦਲਣ ਲਈ, ਈਥਰਨੈੱਟ ਨੂੰ ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ਵਰਤੋਂਕਾਰ ਚੁਣੋ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 3635876..b7fc15d 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -1190,4 +1190,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi nie będzie na razie włączać się automatycznie"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Pokaż wszystko"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Aby przełączać sieci, odłącz Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Wybierz użytkownika"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 8f499f7..ceb6ff5 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"A conexão automática ao Wi-Fi ficará indisponível"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Ver tudo"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para mudar de rede, desconecte o cabo Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecionar usuário"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index b0c65d8..a103e41 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Por agora, o Wi-Fi não irá estabelecer lig. automaticamente"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Veja tudo"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para mudar de rede, desligue a Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecione utilizador"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 8f499f7..ceb6ff5 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"A conexão automática ao Wi-Fi ficará indisponível"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Ver tudo"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para mudar de rede, desconecte o cabo Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecionar usuário"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index c143d53..dba5b7d 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -1184,4 +1184,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Deocamdată, Wi-Fi nu se poate conecta automat"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Afișează-le pe toate"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Pentru a schimba rețeaua, deconectați ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Alegeți utilizatorul"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 752110e..808cb54 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -1190,4 +1190,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Подключение по Wi-Fi не установится автоматически."</string>
<string name="see_all_networks" msgid="3773666844913168122">"Показать все"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Чтобы переключиться между сетями, отключите кабель Ethernet."</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Выберите профиль"</string>
</resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index b3a7a8e3..a81292c 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi දැනට ස්වයං-සබැඳි නොවනු ඇත"</string>
<string name="see_all_networks" msgid="3773666844913168122">"සියල්ල බලන්න"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ජාල මාරු කිරීමට, ඊතර්නෙට් විසන්ධි කරන්න"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"පරිශීලක තෝරන්න"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index f45226b..a2cb0d6 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -1190,4 +1190,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi‑Fi sa teraz automaticky nepripojí"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Zobraziť všetko"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ak chcete prepnúť siete, odpojte ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Vyberte používateľa"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index ed75390..5092c03 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -1190,4 +1190,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Vmesnik Wi-Fi trenutno ne bo samodejno vzpostavil povezave."</string>
<string name="see_all_networks" msgid="3773666844913168122">"Prikaz vseh omrežij"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Če želite preklopiti omrežje, prekinite ethernetno povezavo."</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Izberite uporabnika"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index dc7e825..adb773d 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi nuk do të lidhet automatikisht për momentin"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Shiko të gjitha"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Për të ndërruar rrjetet, shkëput Ethernet-in"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Zgjidh përdoruesin"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index b486589..a3f85c9 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -1184,4 +1184,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"WiFi тренутно не може да се аутоматски повеже"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Погледајте све"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Да бисте променили мрежу, прекините етернет везу"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Изаберите корисника"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 5db089b..6073c7d 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Du ansluts inte till wifi automatiskt för närvarande"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Visa alla"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Koppla bort Ethernet för att växla nätverk"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Välj användare"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 60104f6..5856a61 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi haitaunganishwa kiotomatiki kwa sasa"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Angalia yote"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ili kubadili mitandao, tenganisha ethaneti"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Chagua mtumiaji"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml
new file mode 100644
index 0000000..e4573c6
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-land/config.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<resources>
+ <!-- Max number of columns for quick controls area -->
+ <integer name="controls_max_columns">2</integer>
+
+ <!-- The maximum number of rows in the QuickQSPanel -->
+ <integer name="quick_qs_panel_max_rows">4</integer>
+
+ <!-- The maximum number of tiles in the QuickQSPanel -->
+ <integer name="quick_qs_panel_max_tiles">8</integer>
+
+ <!-- Whether to use the split 2-column notification shade -->
+ <bool name="config_use_split_notification_shade">true</bool>
+
+ <!-- The number of columns in the QuickSettings -->
+ <integer name="quick_settings_num_columns">2</integer>
+
+ <!-- Notifications are sized to match the width of two (of 4) qs tiles in landscape. -->
+ <bool name="config_skinnyNotifsInLandscape">false</bool>
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-port/config.xml b/packages/SystemUI/res/values-sw720dp-port/config.xml
new file mode 100644
index 0000000..1225086
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-port/config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources>
+ <!-- The maximum number of tiles in the QuickQSPanel -->
+ <integer name="quick_qs_panel_max_tiles">6</integer>
+
+ <!-- The number of columns in the QuickSettings -->
+ <integer name="quick_settings_num_columns">3</integer>
+
+ <!-- The maximum number of rows in the QuickSettings -->
+ <integer name="quick_settings_max_rows">3</integer>
+
+</resources>
+
diff --git a/packages/SystemUI/res/values-sw720dp/config.xml b/packages/SystemUI/res/values-sw720dp/config.xml
index ac44251..436f8d0 100644
--- a/packages/SystemUI/res/values-sw720dp/config.xml
+++ b/packages/SystemUI/res/values-sw720dp/config.xml
@@ -22,14 +22,5 @@
<resources>
<integer name="status_bar_config_maxNotificationIcons">5</integer>
- <!-- The maximum number of tiles in the QuickQSPanel -->
- <integer name="quick_qs_panel_max_tiles">6</integer>
-
- <!-- The number of columns in the QuickSettings -->
- <integer name="quick_settings_num_columns">3</integer>
-
- <!-- The maximum number of rows in the QuickSettings -->
- <integer name="quick_settings_max_rows">3</integer>
-
</resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 8cc4c8d..8daa24f 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"தற்போதைக்கு வைஃபை தானாக இணைக்கப்படாது"</string>
<string name="see_all_networks" msgid="3773666844913168122">"அனைத்தையும் காட்டு"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"நெட்வொர்க்குகளை மாற்ற ஈதர்நெட் இணைப்பைத் துண்டிக்கவும்"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"பயனரைத் தேர்வுசெய்க"</string>
</resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 95b8a63..cab7b1c 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"ప్రస్తుతానికి Wi-Fi ఆటోమేటిక్గా కనెక్ట్ అవ్వదు"</string>
<string name="see_all_networks" msgid="3773666844913168122">"అన్నీ చూడండి"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"నెట్వర్క్లను మార్చడానికి, ఈథర్నెట్ను డిస్కనెక్ట్ చేయండి"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"యూజర్ను ఎంచుకోండి"</string>
</resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 91a2440..6da8888 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi จะไม่เชื่อมต่ออัตโนมัติในตอนนี้"</string>
<string name="see_all_networks" msgid="3773666844913168122">"ดูทั้งหมด"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ตัดการเชื่อมต่ออีเทอร์เน็ตเพื่อสลับเครือข่าย"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"เลือกผู้ใช้"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index dcde0fc..a9c5886 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Hindi awtomatikong kokonekta ang Wi-Fi sa ngayon"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Tingnan lahat"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para lumipat ng network, idiskonekta ang ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pumili ng user"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 367d0bf..8fe7862 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Şu anda kablosuz ağa otomatik olarak bağlanılamıyor"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Tümünü göster"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ağ değiştirmek için ethernet bağlantısını kesin"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kullanıcı seçin"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index e692a36..596ba95 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -1190,4 +1190,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Пристрій не підключатиметься до Wi-Fi автоматично"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Показати все"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Щоб вибрати іншу мережу, від’єднайте кабель Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Виберіть користувача"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 44b91ad..f05c709 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"ابھی Wi-Fi خود کار طور پر منسلک نہیں ہوگا"</string>
<string name="see_all_networks" msgid="3773666844913168122">"سبھی دیکھیں"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"نیٹ ورکس پر سوئچ کرنے کیلئے، ایتھرنیٹ غیر منسلک کریں"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"صارف منتخب کریں"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index f0afb6c..e9c20a9 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi hozir avtomatik ulanmaydi"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Hammasi"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Boshqa tarmoqqa almashish uchun Ethernet tarmogʻini uzing"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Foydalanuvchini tanlang"</string>
</resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index b0f1dcf..07a08ad 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Tạm thời, Wi-Fi sẽ không tự động kết nối"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Xem tất cả"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Để chuyển mạng, hãy rút cáp Ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Chọn người dùng"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 7caae5d..377d15c 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"WLAN 暂时无法自动连接"</string>
<string name="see_all_networks" msgid="3773666844913168122">"查看全部"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"如要切换网络,请断开以太网连接"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"选择用户"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 4a8567d..73df468 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"目前系統不會自動連線至 Wi-Fi"</string>
<string name="see_all_networks" msgid="3773666844913168122">"顯示全部"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"如要切換網絡,請中斷以太網連線"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"選取使用者"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index b444d42..f883b10 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"目前不會自動連上 Wi-Fi"</string>
<string name="see_all_networks" msgid="3773666844913168122">"查看全部"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"如要切換網路,請中斷乙太網路連線"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"選取使用者"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 7988476..e1c1f6f 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -1178,4 +1178,5 @@
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"I-Wi-Fi ngeke ixhume ngokuzenzakalelayo okwamanje"</string>
<string name="see_all_networks" msgid="3773666844913168122">"Bona konke"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ukuze ushintshe amanethiwekhi, nqamula i-ethernet"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Khetha umsebenzisi"</string>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 946bbc2..7293f31 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1439,10 +1439,10 @@
<!-- Output switcher panel related dimensions -->
<dimen name="media_output_dialog_list_margin">12dp</dimen>
<dimen name="media_output_dialog_list_max_height">364dp</dimen>
- <dimen name="media_output_dialog_header_album_icon_size">52dp</dimen>
- <dimen name="media_output_dialog_header_back_icon_size">36dp</dimen>
+ <dimen name="media_output_dialog_header_album_icon_size">48dp</dimen>
+ <dimen name="media_output_dialog_header_back_icon_size">32dp</dimen>
<dimen name="media_output_dialog_header_icon_padding">16dp</dimen>
- <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
+ <dimen name="media_output_dialog_icon_corner_radius">8dp</dimen>
<dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen>
<!-- Distance that the full shade transition takes in order for qs to fully transition to the
@@ -1602,7 +1602,7 @@
<!-- Internet panel related dimensions -->
<dimen name="internet_dialog_list_margin">12dp</dimen>
- <dimen name="internet_dialog_list_max_height">646dp</dimen>
+ <dimen name="internet_dialog_list_max_height">662dp</dimen>
<!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
<dimen name="large_dialog_width">@dimen/match_parent</dimen>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index d6d98b9..6758387 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -24,9 +24,6 @@
<bool name="flag_monet">true</bool>
- <!-- AOD/Lockscreen alternate layout -->
- <bool name="flag_keyguard_layout">true</bool>
-
<!-- People Tile flag -->
<bool name="flag_conversations">false</bool>
@@ -46,7 +43,9 @@
<bool name="flag_ongoing_call_status_bar_chip">true</bool>
- <bool name="flag_ongoing_call_in_immersive">false</bool>
+ <bool name="flag_ongoing_call_in_immersive">true</bool>
+
+ <bool name="flag_ongoing_call_in_immersive_chip_tap">true</bool>
<bool name="flag_smartspace">false</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ec63df5..523b9c8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2830,6 +2830,8 @@
<string name="controls_media_settings_button">Settings</string>
<!-- Description for media control's playing media item, including information for the media's title, the artist, and source app [CHAR LIMIT=NONE]-->
<string name="controls_media_playing_item_description"><xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> is playing from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
+ <!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] -->
+ <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string>
<!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] -->
<string name="controls_media_smartspace_rec_title">Play</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6e51ec7..ff299ea 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -417,7 +417,9 @@
<item name="android:windowIsFloating">true</item>
</style>
- <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog" />
+ <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog">
+ <item name="android:buttonCornerRadius">28dp</item>
+ </style>
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
@@ -513,6 +515,10 @@
<item name="android:background">@drawable/btn_borderless_rect</item>
</style>
+ <style name="MediaOutputRoundedOutlinedButton" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/media_output_dialog_button_background</item>
+ </style>
+
<style name="TunerSettings" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="android:windowActionBar">false</item>
<item name="preferenceTheme">@style/TunerPreferenceTheme</item>
@@ -929,26 +935,14 @@
<item name="actionDividerHeight">32dp</item>
</style>
- <style name="Theme.SystemUI.Dialog.QSDialog">
- <item name="android:windowIsTranslucent">true</item>
- <item name="android:windowBackground">@android:color/transparent</item>
- <item name="android:windowIsFloating">true</item>
- <item name="android:backgroundDimEnabled">true</item>
- <item name="android:windowCloseOnTouchOutside">true</item>
- <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
- <item name="android:dialogCornerRadius">28dp</item>
- <item name="android:buttonCornerRadius">28dp</item>
- <item name="android:colorBackground">@color/prv_color_surface</item>
- </style>
-
- <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog.QSDialog">
+ <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog">
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">24sp</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:lineHeight">32sp</item>
</style>
- <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog.QSDialog">
+ <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog">
<item name="android:background">@drawable/qs_dialog_btn_filled</item>
<item name="android:textColor">@color/prv_text_color_on_accent</item>
<item name="android:textSize">14sp</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 11557ad..5b7e500 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -143,5 +143,12 @@
/** Notifies when taskbar status updated */
oneway void notifyTaskbarStatus(boolean visible, boolean stashed) = 47;
- // Next id = 48
+ /**
+ * Notifies sysui when taskbar requests autoHide to stop auto-hiding
+ * If called to suspend, caller is also responsible for calling this method to un-suspend
+ * @param suspend should be true to stop auto-hide, false to resume normal behavior
+ */
+ oneway void notifyTaskbarAutohideSuspend(boolean suspend) = 48;
+
+ // Next id = 49
}
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
new file mode 100644
index 0000000..2ed6328
--- /dev/null
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
@@ -0,0 +1,157 @@
+/*
+ * 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.systemui.flags;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * Concrete implementation of the a Flag manager that returns default values for debug builds
+ *
+ * Flags can be set (or unset) via the following adb command:
+ *
+ * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
+ *
+ * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
+ */
+@SysUISingleton
+public class FeatureFlagManager implements FlagReader, FlagWriter {
+ private static final String TAG = "SysUIFlags";
+
+ private static final String SYSPROP_PREFIX = "persist.systemui.flag_";
+ private static final String FIELD_TYPE = "type";
+ private static final String FIELD_ID = "id";
+ private static final String FIELD_VALUE = "value";
+ private static final String TYPE_BOOLEAN = "boolean";
+ private static final String ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG";
+ private static final String FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS";
+ private final SystemPropertiesHelper mSystemPropertiesHelper;
+
+ @Inject
+ public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context) {
+ mSystemPropertiesHelper = systemPropertiesHelper;
+
+ IntentFilter filter = new IntentFilter(ACTION_SET_FLAG);
+ context.registerReceiver(mReceiver, filter, FLAGS_PERMISSION, null);
+ }
+
+ /** Return a {@link BooleanFlag}'s value. */
+ public boolean isEnabled(int id, boolean defaultValue) {
+ String data = mSystemPropertiesHelper.get(keyToSysPropKey(id));
+ if (data.isEmpty()) {
+ return defaultValue;
+ }
+ JSONObject json;
+ try {
+ json = new JSONObject(data);
+ if (!assertType(json, TYPE_BOOLEAN)) {
+ return defaultValue;
+ }
+ return json.getBoolean(FIELD_VALUE);
+ } catch (JSONException e) {
+ eraseFlag(id);
+ return defaultValue;
+ }
+ }
+
+ /** Set whether a given {@link BooleanFlag} is enabled or not. */
+ public void setEnabled(int id, boolean value) {
+ JSONObject json = new JSONObject();
+ try {
+ json.put(FIELD_TYPE, TYPE_BOOLEAN);
+ json.put(FIELD_VALUE, value);
+ mSystemPropertiesHelper.set(keyToSysPropKey(id), json.toString());
+ Log.i(TAG, "Set id " + id + " to " + value);
+ } catch (JSONException e) {
+ // no-op
+ }
+ }
+
+ /** Erase a flag's overridden value if there is one. */
+ public void eraseFlag(int id) {
+ // We can't actually "erase" things from sysprops, but we can set them to empty!
+ mSystemPropertiesHelper.set(keyToSysPropKey(id), "");
+ Log.i(TAG, "Erase id " + id);
+ }
+
+ public void addListener(Listener run) {}
+
+ public void removeListener(Listener run) {}
+
+ private static String keyToSysPropKey(int key) {
+ return SYSPROP_PREFIX + key;
+ }
+
+ private static boolean assertType(JSONObject json, String type) {
+ try {
+ return json.getString(FIELD_TYPE).equals(TYPE_BOOLEAN);
+ } catch (JSONException e) {
+ return false;
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+
+ if (ACTION_SET_FLAG.equals(action)) {
+ handleSetFlag(intent.getExtras());
+ }
+ }
+
+ private void handleSetFlag(Bundle extras) {
+ int id = extras.getInt(FIELD_ID);
+ if (id <= 0) {
+ Log.w(TAG, "ID not set or less than or equal to 0: " + id);
+ return;
+ }
+
+ Map<Integer, Flag<?>> flagMap = Flags.collectFlags();
+ if (!flagMap.containsKey(id)) {
+ Log.w(TAG, "Tried to set unknown id: " + id);
+ return;
+ }
+ Flag<?> flag = flagMap.get(id);
+
+ if (!extras.containsKey(FIELD_VALUE)) {
+ eraseFlag(id);
+ return;
+ }
+
+ if (flag instanceof BooleanFlag) {
+ setEnabled(id, extras.getBoolean(FIELD_VALUE));
+ }
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index f81f0b9..6fd0c82 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -41,7 +41,7 @@
private static final long CLOCK_OUT_MILLIS = 150;
private static final long CLOCK_IN_MILLIS = 200;
- private static final long SMARTSPACE_MOVE_MILLIS = 350;
+ private static final long STATUS_AREA_MOVE_MILLIS = 350;
@IntDef({LARGE, SMALL})
@Retention(RetentionPolicy.SOURCE)
@@ -63,13 +63,7 @@
private AnimatableClockView mClockView;
private AnimatableClockView mLargeClockView;
- /**
- * Status area (date and other stuff) shown below the clock. Plugin can decide whether or not to
- * show it below the alternate clock.
- */
- private View mKeyguardStatusArea;
- /** Mutually exclusive with mKeyguardStatusArea */
- private View mSmartspaceView;
+ private View mStatusArea;
private int mSmartspaceTopOffset;
/**
@@ -85,7 +79,7 @@
@VisibleForTesting AnimatorSet mClockInAnim = null;
@VisibleForTesting AnimatorSet mClockOutAnim = null;
- private ObjectAnimator mSmartspaceAnim = null;
+ private ObjectAnimator mStatusAreaAnim = null;
/**
* If the Keyguard Slice has a header (big center-aligned text.)
@@ -131,7 +125,7 @@
mClockView = findViewById(R.id.animatable_clock_view);
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
mLargeClockView = findViewById(R.id.animatable_clock_view_large);
- mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
+ mStatusArea = findViewById(R.id.keyguard_status_area);
onDensityOrFontScaleChanged();
}
@@ -200,22 +194,22 @@
private void animateClockChange(boolean useLargeClock) {
if (mClockInAnim != null) mClockInAnim.cancel();
if (mClockOutAnim != null) mClockOutAnim.cancel();
- if (mSmartspaceAnim != null) mSmartspaceAnim.cancel();
+ if (mStatusAreaAnim != null) mStatusAreaAnim.cancel();
View in, out;
int direction = 1;
- float smartspaceYTranslation;
+ float statusAreaYTranslation;
if (useLargeClock) {
out = mClockFrame;
in = mLargeClockFrame;
if (indexOfChild(in) == -1) addView(in);
direction = -1;
- smartspaceYTranslation = mSmartspaceView == null ? 0
- : mClockFrame.getTop() - mSmartspaceView.getTop() + mSmartspaceTopOffset;
+ statusAreaYTranslation = mClockFrame.getTop() - mStatusArea.getTop()
+ + mSmartspaceTopOffset;
} else {
in = mClockFrame;
out = mLargeClockFrame;
- smartspaceYTranslation = 0f;
+ statusAreaYTranslation = 0f;
// Must remove in order for notifications to appear in the proper place
removeView(out);
@@ -251,18 +245,16 @@
mClockInAnim.start();
mClockOutAnim.start();
- if (mSmartspaceView != null) {
- mSmartspaceAnim = ObjectAnimator.ofFloat(mSmartspaceView, View.TRANSLATION_Y,
- smartspaceYTranslation);
- mSmartspaceAnim.setDuration(SMARTSPACE_MOVE_MILLIS);
- mSmartspaceAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mSmartspaceAnim.addListener(new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animation) {
- mSmartspaceAnim = null;
- }
- });
- mSmartspaceAnim.start();
- }
+ mStatusAreaAnim = ObjectAnimator.ofFloat(mStatusArea, View.TRANSLATION_Y,
+ statusAreaYTranslation);
+ mStatusAreaAnim.setDuration(STATUS_AREA_MOVE_MILLIS);
+ mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ mStatusAreaAnim = null;
+ }
+ });
+ mStatusAreaAnim.start();
}
/**
@@ -352,10 +344,6 @@
}
}
- void setSmartspaceView(View smartspaceView) {
- mSmartspaceView = smartspaceView;
- }
-
void updateColors(ColorExtractor.GradientColors colors) {
mSupportsDarkText = colors.supportsDarkText();
mColorPalette = colors.getColorPalette();
@@ -369,8 +357,7 @@
pw.println(" mClockPlugin: " + mClockPlugin);
pw.println(" mClockFrame: " + mClockFrame);
pw.println(" mLargeClockFrame: " + mLargeClockFrame);
- pw.println(" mKeyguardStatusArea: " + mKeyguardStatusArea);
- pw.println(" mSmartspaceView: " + mSmartspaceView);
+ pw.println(" mStatusArea: " + mStatusArea);
pw.println(" mDarkAmount: " + mDarkAmount);
pw.println(" mSupportsDarkText: " + mSupportsDarkText);
pw.println(" mColorPalette: " + Arrays.toString(mColorPalette));
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 4111020..01976b4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -25,7 +25,9 @@
import android.content.res.Resources;
import android.text.TextUtils;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.android.internal.colorextraction.ColorExtractor;
@@ -99,7 +101,8 @@
private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
- // If set, will replace keyguard_status_area
+ private ViewGroup mStatusArea;
+ // If set will replace keyguard_slice_view
private View mSmartspaceView;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@@ -191,8 +194,8 @@
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
if (mOnlyClock) {
- View ksa = mView.findViewById(R.id.keyguard_status_area);
- ksa.setVisibility(View.GONE);
+ View ksv = mView.findViewById(R.id.keyguard_slice_view);
+ ksv.setVisibility(View.GONE);
View nic = mView.findViewById(
R.id.left_aligned_notification_icon_container);
@@ -201,19 +204,18 @@
}
updateAodIcons();
+ mStatusArea = mView.findViewById(R.id.keyguard_status_area);
+
if (mSmartspaceController.isEnabled()) {
mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
+ View ksv = mView.findViewById(R.id.keyguard_slice_view);
+ int ksvIndex = mStatusArea.indexOfChild(ksv);
+ ksv.setVisibility(View.GONE);
- View ksa = mView.findViewById(R.id.keyguard_status_area);
- int ksaIndex = mView.indexOfChild(ksa);
- ksa.setVisibility(View.GONE);
-
- // Place smartspace view below normal clock...
- RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
MATCH_PARENT, WRAP_CONTENT);
- lp.addRule(RelativeLayout.BELOW, R.id.lockscreen_clock_view);
- mView.addView(mSmartspaceView, ksaIndex, lp);
+ mStatusArea.addView(mSmartspaceView, ksvIndex, lp);
int startPadding = getContext().getResources()
.getDimensionPixelSize(R.dimen.below_clock_padding_start);
int endPadding = getContext().getResources()
@@ -221,14 +223,6 @@
mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
updateClockLayout();
-
- View nic = mView.findViewById(
- R.id.left_aligned_notification_icon_container);
- lp = (RelativeLayout.LayoutParams) nic.getLayoutParams();
- lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId());
- nic.setLayoutParams(lp);
-
- mView.setSmartspaceView(mSmartspaceView);
mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceView);
}
}
@@ -244,8 +238,6 @@
}
mColorExtractor.removeOnColorsChangedListener(mColorsListener);
mView.setClockPlugin(null, mStatusBarStateController.getState());
-
- mSmartspaceController.disconnect();
}
/**
@@ -328,8 +320,8 @@
PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
scale, props, animate);
- if (mSmartspaceView != null) {
- PropertyAnimator.setProperty(mSmartspaceView, AnimatableProperty.TRANSLATION_X,
+ if (mStatusArea != null) {
+ PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
x, props, animate);
// If we're unlocking with the SmartSpace shared element transition, let the controller
@@ -340,7 +332,6 @@
}
mKeyguardSliceViewController.updatePosition(x, props, animate);
- mNotificationIconAreaController.updatePosition(x, props, animate);
}
/** Sets an alpha value on every child view except for the smartspace. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 9d649e7..d4d3d5b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -209,7 +209,7 @@
private ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
mSecurityViewFlipperController.reloadColors();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 428006e..9b76bab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -33,12 +33,10 @@
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
-import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.animation.Animation;
import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.slice.SliceItem;
@@ -85,8 +83,6 @@
private boolean mHasHeader;
private View.OnClickListener mOnClickListener;
- private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
-
public KeyguardSliceView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -136,35 +132,6 @@
}
}
- /**
- * Updates the lockscreen mode which may change the layout of the keyguard slice view.
- */
- public void updateLockScreenMode(int mode) {
- mLockScreenMode = mode;
- if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- mTitle.setPaddingRelative(0, 0, 0, 0);
- mTitle.setGravity(Gravity.START);
- setGravity(Gravity.START);
- RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams();
- lp.removeRule(RelativeLayout.CENTER_HORIZONTAL);
- setLayoutParams(lp);
- } else {
- final int horizontalPaddingDpValue = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP,
- 44,
- getResources().getDisplayMetrics()
- );
- mTitle.setPaddingRelative(horizontalPaddingDpValue, 0, horizontalPaddingDpValue, 0);
- mTitle.setGravity(Gravity.CENTER_HORIZONTAL);
- setGravity(Gravity.CENTER_HORIZONTAL);
- RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams();
- lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
- setLayoutParams(lp);
- }
- mRow.setLockscreenMode(mode);
- requestLayout();
- }
-
Map<View, PendingIntent> showSlice(RowContent header, List<SliceContent> subItems) {
Trace.beginSection("KeyguardSliceView#showSlice");
mHasHeader = header != null;
@@ -189,8 +156,7 @@
final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it
mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE);
LinearLayout.LayoutParams layoutParams = (LayoutParams) mRow.getLayoutParams();
- layoutParams.gravity = mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL
- ? Gravity.START : Gravity.CENTER;
+ layoutParams.gravity = Gravity.START;
mRow.setLayoutParams(layoutParams);
for (int i = startIndex; i < subItemsCount; i++) {
@@ -224,8 +190,7 @@
final int iconSize = mHasHeader ? mIconSizeWithHeader : mIconSize;
iconDrawable = icon.getIcon().loadDrawable(mContext);
if (iconDrawable != null) {
- if ((iconDrawable instanceof InsetDrawable)
- && mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ if (iconDrawable instanceof InsetDrawable) {
// System icons (DnD) use insets which are fine for centered slice content
// but will cause a slight indent for left/right-aligned slice views
iconDrawable = ((InsetDrawable) iconDrawable).getDrawable();
@@ -321,7 +286,6 @@
pw.println(" mTextColor: " + Integer.toHexString(mTextColor));
pw.println(" mDarkAmount: " + mDarkAmount);
pw.println(" mHasHeader: " + mHasHeader);
- pw.println(" mLockScreenMode: " + mLockScreenMode);
}
@Override
@@ -332,7 +296,6 @@
public static class Row extends LinearLayout {
private Set<KeyguardSliceTextView> mKeyguardSliceTextViewSet = new HashSet();
- private int mLockScreenModeRow = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
/**
* This view is visible in AOD, which means that the device will sleep if we
@@ -407,11 +370,7 @@
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child instanceof KeyguardSliceTextView) {
- if (mLockScreenModeRow == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- ((KeyguardSliceTextView) child).setMaxWidth(Integer.MAX_VALUE);
- } else {
- ((KeyguardSliceTextView) child).setMaxWidth(width / 3);
- }
+ ((KeyguardSliceTextView) child).setMaxWidth(Integer.MAX_VALUE);
}
}
@@ -443,7 +402,6 @@
super.addView(view, index);
if (view instanceof KeyguardSliceTextView) {
- ((KeyguardSliceTextView) view).setLockScreenMode(mLockScreenModeRow);
mKeyguardSliceTextViewSet.add((KeyguardSliceTextView) view);
}
}
@@ -455,24 +413,6 @@
mKeyguardSliceTextViewSet.remove((KeyguardSliceTextView) view);
}
}
-
- /**
- * Updates the lockscreen mode which may change the layout of this view.
- */
- public void setLockscreenMode(int mode) {
- mLockScreenModeRow = mode;
- if (mLockScreenModeRow == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- setOrientation(LinearLayout.VERTICAL);
- setGravity(Gravity.START);
- } else {
- setOrientation(LinearLayout.HORIZONTAL);
- setGravity(Gravity.CENTER);
- }
-
- for (KeyguardSliceTextView textView : mKeyguardSliceTextViewSet) {
- textView.setLockScreenMode(mLockScreenModeRow);
- }
- }
}
/**
@@ -480,7 +420,6 @@
*/
@VisibleForTesting
static class KeyguardSliceTextView extends TextView {
- private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
@StyleRes
private static int sStyleId = R.style.TextAppearance_Keyguard_Secondary;
@@ -509,13 +448,8 @@
boolean hasText = !TextUtils.isEmpty(getText());
int padding = (int) getContext().getResources()
.getDimension(R.dimen.widget_horizontal_padding) / 2;
- if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- // orientation is vertical, so add padding to top & bottom
- setPadding(0, padding, 0, hasText ? padding : 0);
- } else {
- // orientation is horizontal, so add padding to left & right
- setPadding(padding, 0, padding * (hasText ? 1 : -1), 0);
- }
+ // orientation is vertical, so add padding to top & bottom
+ setPadding(0, padding, 0, hasText ? padding : 0);
setCompoundDrawablePadding((int) mContext.getResources()
.getDimension(R.dimen.widget_icon_padding));
@@ -543,18 +477,5 @@
}
}
}
-
- /**
- * Updates the lockscreen mode which may change the layout of this view.
- */
- public void setLockScreenMode(int mode) {
- mLockScreenMode = mode;
- if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
- } else {
- setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
- }
- updatePadding();
- }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index 8038ce4..d05cc4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -73,7 +73,6 @@
private Uri mKeyguardSliceUri;
private Slice mSlice;
private Map<View, PendingIntent> mClickActions;
- private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
TunerService.Tunable mTunable = (key, newValue) -> setupUri(newValue);
@@ -84,7 +83,7 @@
mView.onDensityOrFontScaleChanged();
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
mView.onOverlayChanged();
}
};
@@ -137,7 +136,6 @@
TAG + "@" + Integer.toHexString(
KeyguardSliceViewController.this.hashCode()),
KeyguardSliceViewController.this);
- mView.updateLockScreenMode(mLockScreenMode);
}
@Override
@@ -160,14 +158,6 @@
}
/**
- * Updates the lockscreen mode which may change the layout of the keyguard slice view.
- */
- public void updateLockScreenMode(int mode) {
- mLockScreenMode = mode;
- mView.updateLockScreenMode(mLockScreenMode);
- }
-
- /**
* Sets the slice provider Uri.
*/
public void setupUri(String uriString) {
@@ -249,6 +239,5 @@
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println(" mSlice: " + mSlice);
pw.println(" mClickActions: " + mClickActions);
- pw.println(" mLockScreenMode: " + mLockScreenMode);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 2362a1a..a72a050e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -88,7 +88,7 @@
mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext));
}
- mKeyguardSlice = findViewById(R.id.keyguard_status_area);
+ mKeyguardSlice = findViewById(R.id.keyguard_slice_view);
mTextColor = mClockView.getCurrentTextColor();
mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index d23b1c8..c58710c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -249,11 +249,6 @@
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@Override
- public void onLockScreenModeChanged(int mode) {
- mKeyguardSliceViewController.updateLockScreenMode(mode);
- }
-
- @Override
public void onTimeChanged() {
refreshTime();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 5969e92..5c0fb5d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -71,7 +71,6 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.Vibrator;
import android.provider.Settings;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
@@ -86,7 +85,6 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import androidx.annotation.Nullable;
import androidx.lifecycle.Observer;
import com.android.internal.annotations.VisibleForTesting;
@@ -98,7 +96,6 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -191,9 +188,6 @@
private static final int MSG_TIME_FORMAT_UPDATE = 344;
private static final int MSG_REQUIRE_NFC_UNLOCK = 345;
- public static final int LOCK_SCREEN_MODE_NORMAL = 0;
- public static final int LOCK_SCREEN_MODE_LAYOUT_1 = 1;
-
/** Biometric authentication state: Not listening. */
private static final int BIOMETRIC_STATE_STOPPED = 0;
@@ -286,9 +280,6 @@
@VisibleForTesting
protected boolean mTelephonyCapable;
- private final boolean mAcquiredHapticEnabled = false;
- @Nullable private final Vibrator mVibrator;
-
// Device provisioning state
private boolean mDeviceProvisioned;
@@ -1407,7 +1398,6 @@
@VisibleForTesting
final FingerprintManager.AuthenticationCallback mFingerprintAuthenticationCallback
= new AuthenticationCallback() {
- private boolean mPlayedAcquiredHaptic;
@Override
public void onAuthenticationFailed() {
@@ -1419,11 +1409,6 @@
Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
handleFingerprintAuthenticated(result.getUserId(), result.isStrongBiometric());
Trace.endSection();
-
- // on auth success, we sometimes never received an acquired haptic
- if (!mPlayedAcquiredHaptic && isUdfpsEnrolled()) {
- playAcquiredHaptic();
- }
}
@Override
@@ -1439,17 +1424,11 @@
@Override
public void onAuthenticationAcquired(int acquireInfo) {
handleFingerprintAcquired(acquireInfo);
- if (acquireInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD
- && isUdfpsEnrolled()) {
- mPlayedAcquiredHaptic = true;
- playAcquiredHaptic();
- }
}
@Override
public void onUdfpsPointerDown(int sensorId) {
Log.d(TAG, "onUdfpsPointerDown, sensorId: " + sensorId);
- mPlayedAcquiredHaptic = false;
}
@Override
@@ -1458,17 +1437,6 @@
}
};
- /**
- * Play haptic to signal udfps fingeprrint acquired.
- */
- @VisibleForTesting
- public void playAcquiredHaptic() {
- if (mAcquiredHapticEnabled && mVibrator != null) {
- mVibrator.vibrate(UdfpsController.EFFECT_CLICK,
- UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES);
- }
- }
-
private final FaceManager.FaceDetectionCallback mFaceDetectionCallback
= (sensorId, userId, isStrongBiometric) -> {
// Trigger the face success path so the bouncer can be shown
@@ -1772,8 +1740,8 @@
AuthController authController,
TelephonyListenerManager telephonyListenerManager,
FeatureFlags featureFlags,
- InteractionJankMonitor interactionJankMonitor,
- @Nullable Vibrator vibrator) {
+ InteractionJankMonitor interactionJankMonitor
+ ) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mTelephonyListenerManager = telephonyListenerManager;
@@ -1789,7 +1757,6 @@
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
dumpManager.registerDumpable(getClass().getName(), this);
- mVibrator = vibrator;
mHandler = new Handler(mainLooper) {
@Override
@@ -1898,9 +1865,6 @@
case MSG_KEYGUARD_GOING_AWAY:
handleKeyguardGoingAway((boolean) msg.obj);
break;
- case MSG_LOCK_SCREEN_MODE:
- handleLockScreenMode();
- break;
case MSG_TIME_FORMAT_UPDATE:
handleTimeFormatUpdate((String) msg.obj);
break;
@@ -2042,8 +2006,6 @@
}
}
- updateLockScreenMode(featureFlags.isKeyguardLayoutEnabled());
-
mTimeFormatChangeObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -2059,14 +2021,6 @@
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
}
- private void updateLockScreenMode(boolean isEnabled) {
- final int newMode = isEnabled ? LOCK_SCREEN_MODE_LAYOUT_1 : LOCK_SCREEN_MODE_NORMAL;
- if (newMode != mLockScreenMode) {
- mLockScreenMode = newMode;
- mHandler.sendEmptyMessage(MSG_LOCK_SCREEN_MODE);
- }
- }
-
private void updateUdfpsEnrolled(int userId) {
mIsUdfpsEnrolled = mAuthController.isUdfpsEnrolled(userId);
}
@@ -2722,20 +2676,6 @@
}
/**
- * Handle {@link #MSG_LOCK_SCREEN_MODE}
- */
- private void handleLockScreenMode() {
- Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleLockScreenMode(" + mLockScreenMode + ")");
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onLockScreenModeChanged(mLockScreenMode);
- }
- }
- }
-
- /**
* Handle (@line #MSG_TIMEZONE_UPDATE}
*/
private void handleTimeZoneUpdate(String timeZone) {
@@ -3113,7 +3053,6 @@
callback.onKeyguardOccludedChanged(mKeyguardOccluded);
callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible);
callback.onTelephonyCapable(mTelephonyCapable);
- callback.onLockScreenModeChanged(mLockScreenMode);
for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
final SimData state = data.getValue();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 6aa7aaa..1e951f9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -330,11 +330,6 @@
public void onSecondaryLockscreenRequirementChanged(int userId) { }
/**
- * Called to switch lock screen layout/clock layouts
- */
- public void onLockScreenModeChanged(int mode) { }
-
- /**
* Called when notifying user to unlock in order to use NFC.
*/
public void onRequireUnlockForNfc() { }
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 8cfd225..321c1a3 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -22,8 +22,8 @@
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset;
-import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -38,6 +38,7 @@
import android.util.MathUtils;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -98,9 +99,10 @@
@NonNull private final AccessibilityManager mAccessibilityManager;
@NonNull private final ConfigurationController mConfigurationController;
@NonNull private final DelayableExecutor mExecutor;
+ @NonNull private final LayoutInflater mLayoutInflater;
private boolean mUdfpsEnrolled;
- @NonNull private LottieAnimationView mAodFp;
+ @Nullable private LottieAnimationView mAodFp;
@NonNull private final AnimatedVectorDrawable mFpToUnlockIcon;
@NonNull private final AnimatedVectorDrawable mLockToUnlockIcon;
@@ -154,7 +156,9 @@
@NonNull ConfigurationController configurationController,
@NonNull @Main DelayableExecutor executor,
@Nullable Vibrator vibrator,
- @Nullable AuthRippleController authRippleController
+ @Nullable AuthRippleController authRippleController,
+ @NonNull @Main Resources resources,
+ @NonNull LayoutInflater inflater
) {
super(view);
mStatusBarStateController = statusBarStateController;
@@ -168,27 +172,19 @@
mExecutor = executor;
mVibrator = vibrator;
mAuthRippleController = authRippleController;
+ mLayoutInflater = inflater;
- final Context context = view.getContext();
- mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
- mMaxBurnInOffsetX = context.getResources()
- .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
- mMaxBurnInOffsetY = context.getResources()
- .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
+ mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
+ mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
- mUnlockIcon = mView.getContext().getResources().getDrawable(
- R.drawable.ic_unlock,
- mView.getContext().getTheme());
- mLockIcon = mView.getContext().getResources().getDrawable(
- R.anim.lock_to_unlock,
- mView.getContext().getTheme());
- mFpToUnlockIcon = (AnimatedVectorDrawable) mView.getContext().getResources().getDrawable(
+ mUnlockIcon = resources.getDrawable(R.drawable.ic_unlock, mView.getContext().getTheme());
+ mLockIcon = resources.getDrawable(R.anim.lock_to_unlock, mView.getContext().getTheme());
+ mFpToUnlockIcon = (AnimatedVectorDrawable) resources.getDrawable(
R.anim.fp_to_unlock, mView.getContext().getTheme());
- mLockToUnlockIcon = (AnimatedVectorDrawable) mView.getContext().getResources().getDrawable(
- R.anim.lock_to_unlock,
+ mLockToUnlockIcon = (AnimatedVectorDrawable) resources.getDrawable(R.anim.lock_to_unlock,
mView.getContext().getTheme());
- mUnlockedLabel = context.getResources().getString(R.string.accessibility_unlock_button);
- mLockedLabel = context.getResources().getString(R.string.accessibility_lock_icon);
+ mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button);
+ mLockedLabel = resources.getString(R.string.accessibility_lock_icon);
dumpManager.registerDumpable("LockIconViewController", this);
}
@@ -264,8 +260,8 @@
boolean wasShowingLockIcon = mShowLockIcon;
boolean wasShowingUnlockIcon = mShowUnlockIcon;
mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
- && (!mUdfpsEnrolled || !mRunningFPS);
- mShowUnlockIcon = mCanDismissLockScreen && isLockScreen();
+ && (!mUdfpsEnrolled || !mRunningFPS);
+ mShowUnlockIcon = (mCanDismissLockScreen || mUserUnlockedWithBiometric) && isLockScreen();
mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS;
final CharSequence prevContentDescription = mView.getContentDescription();
@@ -300,7 +296,7 @@
mView.setContentDescription(null);
}
- if (!mShowAODFpIcon) {
+ if (!mShowAODFpIcon && mAodFp != null) {
mAodFp.setVisibility(View.INVISIBLE);
mAodFp.setContentDescription(null);
}
@@ -416,10 +412,12 @@
- mMaxBurnInOffsetY, mInterpolatedDarkAmount);
float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount);
- mAodFp.setTranslationX(offsetX);
- mAodFp.setTranslationY(offsetY);
- mAodFp.setProgress(progress);
- mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
+ if (mAodFp != null) {
+ mAodFp.setTranslationX(offsetX);
+ mAodFp.setTranslationY(offsetY);
+ mAodFp.setProgress(progress);
+ mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
+ }
}
private void updateIsUdfpsEnrolled() {
@@ -430,6 +428,10 @@
mView.setUseBackground(mUdfpsSupported);
mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
+ if (!wasUdfpsEnrolled && mUdfpsEnrolled && mAodFp == null) {
+ mLayoutInflater.inflate(R.layout.udfps_aod_lock_icon, mView);
+ mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
+ }
if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) {
updateVisibility();
}
@@ -477,13 +479,15 @@
@Override
public void onBiometricRunningStateChanged(boolean running,
BiometricSourceType biometricSourceType) {
+ final boolean wasRunningFps = mRunningFPS;
+ final boolean wasUserUnlockedWithBiometric = mUserUnlockedWithBiometric;
mUserUnlockedWithBiometric =
mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
KeyguardUpdateMonitor.getCurrentUser());
if (biometricSourceType == FINGERPRINT) {
mRunningFPS = running;
- if (!mRunningFPS) {
+ if (wasRunningFps && !mRunningFPS) {
if (mCancelDelayedUpdateVisibilityRunnable != null) {
mCancelDelayedUpdateVisibilityRunnable.run();
}
@@ -493,10 +497,14 @@
// button in this case, so we delay updating the visibility by 50ms.
mCancelDelayedUpdateVisibilityRunnable =
mExecutor.executeDelayed(() -> updateVisibility(), 50);
- } else {
- updateVisibility();
+ return;
}
}
+
+ if (wasUserUnlockedWithBiometric != mUserUnlockedWithBiometric
+ || wasRunningFps != mRunningFPS) {
+ updateVisibility();
+ }
}
};
@@ -545,11 +553,6 @@
}
@Override
- public void onOverlayChanged() {
- updateColors();
- }
-
- @Override
public void onConfigChanged(Configuration newConfig) {
updateConfiguration();
updateColors();
@@ -650,7 +653,7 @@
public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
&& (mView.getVisibility() == View.VISIBLE
- || mAodFp.getVisibility() == View.VISIBLE)) {
+ || (mAodFp != null && mAodFp.getVisibility() == View.VISIBLE))) {
mOnGestureDetectedRunnable = onGestureDetectedRunnable;
mGestureDetector.onTouchEvent(event);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
index 1d51e59..b8841ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
@@ -34,6 +34,6 @@
@Provides
static KeyguardSliceView getKeyguardSliceView(KeyguardClockSwitch keyguardClockSwitch) {
- return keyguardClockSwitch.findViewById(R.id.keyguard_status_area);
+ return keyguardClockSwitch.findViewById(R.id.keyguard_slice_view);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
index 5ed9eaa..12dd8f0 100644
--- a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
@@ -86,7 +86,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
inflateLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 0932a8c..8b04bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -272,9 +272,6 @@
override fun onThemeChanged() {
updateRippleColor()
}
- override fun onOverlayChanged() {
- updateRippleColor()
- }
}
private val udfpsControllerCallback =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 594a642..b2eaa2b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -256,6 +256,13 @@
@Override
public void hideUdfpsOverlay(int sensorId) {
mFgExecutor.execute(() -> {
+ if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
+ // if we get here, we expect keyguardUpdateMonitor's fingerprintRunningState
+ // to be updated shortly afterwards
+ Log.d(TAG, "hiding udfps overlay when "
+ + "mKeyguardUpdateMonitor.isFingerprintDetectionRunning()=true");
+ }
+
mServerRequest = null;
updateOverlay();
});
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
index ea2bbfa..e231310 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
@@ -58,9 +58,6 @@
"start" -> {
udfpsController?.playStartHaptic()
}
- "acquired" -> {
- keyguardUpdateMonitor.playAcquiredHaptic()
- }
"success" -> {
// needs to be kept up to date with AcquisitionClient#SUCCESS_VIBRATION_EFFECT
vibrator?.vibrate(
@@ -82,7 +79,6 @@
pw.println("Usage: adb shell cmd statusbar udfps-haptic <haptic>")
pw.println("Available commands:")
pw.println(" start")
- pw.println(" acquired")
pw.println(" success, always plays CLICK haptic")
pw.println(" error, always plays DOUBLE_CLICK haptic")
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index db93b26..7a28c9d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -398,11 +398,6 @@
}
@Override
- public void onOverlayChanged() {
- mView.updateColor();
- }
-
- @Override
public void onConfigChanged(Configuration newConfig) {
mView.updateColor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 3b459d1..f933128 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -38,7 +38,10 @@
import com.android.systemui.demomode.dagger.DemoModeModule;
import com.android.systemui.doze.dagger.DozeComponent;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlagManager;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FlagReader;
+import com.android.systemui.flags.FlagWriter;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.model.SysUiState;
@@ -149,6 +152,12 @@
return state;
}
+ @Binds
+ abstract FlagReader provideFlagReader(FeatureFlagManager impl);
+
+ @Binds
+ abstract FlagWriter provideFlagWriter(FeatureFlagManager impl);
+
@BindsOptionalOf
abstract CommandQueue optionalCommandQueue();
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java
new file mode 100644
index 0000000..85baed4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java
@@ -0,0 +1,38 @@
+/*
+ * 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.systemui.flags;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Default implementation of the a Flag manager that returns default values for release builds
+ */
+@SysUISingleton
+public class FeatureFlagManager implements FlagReader, FlagWriter {
+ @Inject
+ public FeatureFlagManager() {}
+ public boolean isEnabled(String key, boolean defaultValue) {
+ return defaultValue;
+ }
+ public boolean isEnabled(int key, boolean defaultValue) {
+ return defaultValue;
+ }
+ public void setEnabled(String key, boolean value) {}
+ public void setEnabled(int key, boolean value) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
index d4d01c8..e78646a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
@@ -16,26 +16,29 @@
package com.android.systemui.flags;
-import android.content.Context;
import android.content.res.Resources;
import android.util.SparseArray;
import androidx.annotation.BoolRes;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.FlagReaderPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.wrapper.BuildInfo;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
import javax.inject.Inject;
/**
* Reads and caches feature flags for quick access
*
- * Feature flags must be defined as boolean resources. For example:
+ * Feature flags must be defined as boolean resources. For example:t
*
* {@code
* <bool name="flag_foo_bar_baz">false</bool>
@@ -55,71 +58,39 @@
* Calls to this class should probably be wrapped by a method in {@link FeatureFlags}.
*/
@SysUISingleton
-public class FeatureFlagReader {
+public class FeatureFlagReader implements Dumpable {
private final Resources mResources;
private final boolean mAreFlagsOverrideable;
- private final PluginManager mPluginManager;
private final SystemPropertiesHelper mSystemPropertiesHelper;
private final SparseArray<CachedFlag> mCachedFlags = new SparseArray<>();
- private FlagReaderPlugin mPlugin = new FlagReaderPlugin(){};
+ private final FlagReader mFlagReader;
@Inject
public FeatureFlagReader(
@Main Resources resources,
BuildInfo build,
- PluginManager pluginManager,
- SystemPropertiesHelper systemPropertiesHelper) {
+ DumpManager dumpManager,
+ SystemPropertiesHelper systemPropertiesHelper,
+ FlagReader reader) {
mResources = resources;
- mPluginManager = pluginManager;
+ mFlagReader = reader;
mSystemPropertiesHelper = systemPropertiesHelper;
mAreFlagsOverrideable =
build.isDebuggable() && mResources.getBoolean(R.bool.are_flags_overrideable);
-
- mPluginManager.addPluginListener(mPluginListener, FlagReaderPlugin.class);
+ dumpManager.registerDumpable("FeatureFlags", this);
}
- private final PluginListener<FlagReaderPlugin> mPluginListener =
- new PluginListener<FlagReaderPlugin>() {
- public void onPluginConnected(FlagReaderPlugin plugin, Context context) {
- mPlugin = plugin;
- }
-
- public void onPluginDisconnected(FlagReaderPlugin plugin) {
- mPlugin = new FlagReaderPlugin() {};
- }
- };
-
boolean isEnabled(BooleanFlag flag) {
- return mPlugin.isEnabled(flag.getId(), flag.getDefault());
+ return mFlagReader.isEnabled(flag.getId(), flag.getDefault());
}
- String getValue(StringFlag flag) {
- return mPlugin.getValue(flag.getId(), flag.getDefault());
+ void addListener(FlagReader.Listener listener) {
+ mFlagReader.addListener(listener);
}
- int getValue(IntFlag flag) {
- return mPlugin.getValue(flag.getId(), flag.getDefault());
- }
-
- long getValue(LongFlag flag) {
- return mPlugin.getValue(flag.getId(), flag.getDefault());
- }
-
- float getValue(FloatFlag flag) {
- return mPlugin.getValue(flag.getId(), flag.getDefault());
- }
-
- double getValue(DoubleFlag flag) {
- return mPlugin.getValue(flag.getId(), flag.getDefault());
- }
-
- void addListener(FlagReaderPlugin.Listener listener) {
- mPlugin.addListener(listener);
- }
-
- void removeListener(FlagReaderPlugin.Listener listener) {
- mPlugin.removeListener(listener);
+ void removeListener(FlagReader.Listener listener) {
+ mFlagReader.removeListener(listener);
}
/**
@@ -172,6 +143,23 @@
}
}
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ ArrayList<String> flagStrings = new ArrayList<>(mCachedFlags.size());
+ for (int i = 0; i < mCachedFlags.size(); i++) {
+ int key = mCachedFlags.keyAt(i);
+ // get the object by the key.
+ CachedFlag flag = mCachedFlags.get(key);
+ flagStrings.add(" " + RESNAME_PREFIX + flag.name + ": " + flag.value + "\n");
+ }
+ flagStrings.sort(String.CASE_INSENSITIVE_ORDER);
+ pw.println("AreFlagsOverrideable: " + mAreFlagsOverrideable);
+ pw.println("Cached FeatureFlags:");
+ for (String flagString : flagStrings) {
+ pw.print(flagString);
+ }
+ }
+
private static class CachedFlag {
public final String name;
public final boolean value;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index f81811a..105e7f5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -22,7 +22,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.plugins.FlagReaderPlugin;
import java.util.ArrayList;
import java.util.HashMap;
@@ -51,7 +50,7 @@
flagReader.addListener(mListener);
}
- private final FlagReaderPlugin.Listener mListener = id -> {
+ private final FlagReader.Listener mListener = id -> {
if (mListeners.containsKey(id) && mFlagMap.containsKey(id)) {
mListeners.get(id).forEach(listener -> listener.onFlagChanged(mFlagMap.get(id)));
}
@@ -71,44 +70,7 @@
}
/**
- * @param flag The {@link StringFlag} of interest.
- * @return The value of the flag.
- */
- public String getValue(StringFlag flag) {
- return mFlagReader.getValue(flag);
- }
-
- /**
* @param flag The {@link IntFlag} of interest.
- * @return The value of the flag.
- */
- public int getValue(IntFlag flag) {
- return mFlagReader.getValue(flag);
- }
-
- /**
- * @param flag The {@link LongFlag} of interest.
- * @return The value of the flag.
- */
- public long getValue(LongFlag flag) {
- return mFlagReader.getValue(flag);
- }
-
- /**
- * @param flag The {@link FloatFlag} of interest.
- * @return The value of the flag.
- */
- public float getValue(FloatFlag flag) {
- return mFlagReader.getValue(flag);
- }
-
- /**
- * @param flag The {@link DoubleFlag} of interest.
- * @return The value of the flag.
- */
- public double getValue(DoubleFlag flag) {
- return mFlagReader.getValue(flag);
- }
/** Add a listener for a specific flag. */
public void addFlagListener(Flag<?> flag, Listener listener) {
@@ -132,10 +94,6 @@
return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2_rendering);
}
- public boolean isKeyguardLayoutEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_keyguard_layout);
- }
-
/** */
public boolean useNewLockscreenAnimations() {
return mFlagReader.isEnabled(R.bool.flag_lockscreen_animations);
@@ -166,6 +124,11 @@
&& mFlagReader.isEnabled(R.bool.flag_ongoing_call_in_immersive);
}
+ public boolean isOngoingCallInImmersiveChipTapEnabled() {
+ return isOngoingCallInImmersiveEnabled()
+ && mFlagReader.isEnabled(R.bool.flag_ongoing_call_in_immersive_chip_tap);
+ }
+
public boolean isSmartspaceEnabled() {
return mFlagReader.isEnabled(R.bool.flag_smartspace);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagReader.java b/packages/SystemUI/src/com/android/systemui/flags/FlagReader.java
new file mode 100644
index 0000000..1ae8c1f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagReader.java
@@ -0,0 +1,40 @@
+/*
+ * 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.systemui.flags;
+
+
+/**
+ * Plugin for loading flag values
+ */
+public interface FlagReader {
+ /** Returns a boolean value for the given flag. */
+ default boolean isEnabled(int id, boolean def) {
+ return def;
+ }
+
+ /** Add a listener to be alerted when any flag changes. */
+ default void addListener(Listener listener) {}
+
+ /** Remove a listener to be alerted when any flag changes. */
+ default void removeListener(Listener listener) {}
+
+ /** A simple listener to be alerted when a flag changes. */
+ interface Listener {
+ /** */
+ void onFlagChanged(int id);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagWriter.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagWriter.kt
new file mode 100644
index 0000000..bacc66b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagWriter.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.systemui.flags
+
+interface FlagWriter {
+ fun setEnabled(key: Int, value: Boolean) {}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 6561bd5..1dc5a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -26,11 +26,19 @@
*/
@SysUISingleton
open class SystemPropertiesHelper @Inject constructor() {
+ fun get(name: String): String {
+ return SystemProperties.get(name)
+ }
+
fun getBoolean(name: String, default: Boolean): Boolean {
return SystemProperties.getBoolean(name, default)
}
+ fun set(name: String, value: String) {
+ SystemProperties.set(name, value)
+ }
+
fun set(name: String, value: Int) {
- SystemProperties.set(name, value.toString())
+ set(name, value.toString())
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 84c5a57..72601e9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -111,6 +111,17 @@
return factory.create("CollapsedSbFragmentLog", 20);
}
+ /**
+ * Provides a logging buffer for logs related to swiping away the status bar while in immersive
+ * mode. See {@link com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureLogger}.
+ */
+ @Provides
+ @SysUISingleton
+ @SwipeStatusBarAwayLog
+ public static LogBuffer provideSwipeAwayGestureLogBuffer(LogBufferFactory factory) {
+ return factory.create("SwipeStatusBarAwayLog", 30);
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
new file mode 100644
index 0000000..dd68375
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
@@ -0,0 +1,36 @@
+/*
+ * 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 com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureLogger}.
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface SwipeStatusBarAwayLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 0e70945..e87558e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -148,7 +148,7 @@
inflateSettingsButton()
}
- override fun onOverlayChanged() {
+ override fun onThemeChanged() {
recreatePlayers()
inflateSettingsButton()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 424f801..e73eb66 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -55,6 +55,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.util.animation.TransitionLayout;
@@ -119,6 +120,7 @@
private int mSmartspaceMediaItemsCount;
private MediaCarouselController mMediaCarouselController;
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final FalsingManager mFalsingManager;
/**
* Initialize a new control panel
@@ -131,7 +133,8 @@
ActivityStarter activityStarter, MediaViewController mediaViewController,
SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory
- mediaOutputDialogFactory, MediaCarouselController mediaCarouselController) {
+ mediaOutputDialogFactory, MediaCarouselController mediaCarouselController,
+ FalsingManager falsingManager) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
@@ -141,6 +144,7 @@
mKeyguardDismissUtil = keyguardDismissUtil;
mMediaOutputDialogFactory = mediaOutputDialogFactory;
mMediaCarouselController = mediaCarouselController;
+ mFalsingManager = falsingManager;
loadDimens();
mSeekBarViewModel.setLogSmartspaceClick(() -> {
@@ -235,10 +239,14 @@
}
});
mPlayerViewHolder.getCancel().setOnClickListener(v -> {
- closeGuts();
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ closeGuts();
+ }
});
mPlayerViewHolder.getSettings().setOnClickListener(v -> {
- mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
+ }
});
}
@@ -259,10 +267,14 @@
}
});
mRecommendationViewHolder.getCancel().setOnClickListener(v -> {
- closeGuts();
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ closeGuts();
+ }
});
mRecommendationViewHolder.getSettings().setOnClickListener(v -> {
- mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
+ }
});
}
@@ -299,6 +311,7 @@
PendingIntent clickIntent = data.getClickIntent();
if (clickIntent != null) {
mPlayerViewHolder.getPlayer().setOnClickListener(v -> {
+ if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
if (mMediaViewController.isGutsVisible()) return;
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
@@ -365,8 +378,12 @@
setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
seamlessView.setOnClickListener(
- v -> mMediaOutputDialogFactory.create(data.getPackageName(), true,
- mPlayerViewHolder.getSeamlessButton()));
+ v -> {
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ mMediaOutputDialogFactory.create(data.getPackageName(), true,
+ mPlayerViewHolder.getSeamlessButton());
+ }
+ });
ImageView iconView = mPlayerViewHolder.getSeamlessIcon();
TextView deviceName = mPlayerViewHolder.getSeamlessText();
@@ -417,9 +434,11 @@
} else {
button.setEnabled(true);
button.setOnClickListener(v -> {
- logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
- /* isRecommendationCard */ false);
- action.run();
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
+ /* isRecommendationCard */ false);
+ action.run();
+ }
});
}
boolean visibleInCompat = actionsWhenCollapsed.contains(i);
@@ -451,6 +470,8 @@
mPlayerViewHolder.getDismissLabel().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
mPlayerViewHolder.getDismiss().setEnabled(isDismissible);
mPlayerViewHolder.getDismiss().setOnClickListener(v -> {
+ if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
+
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
/* isRecommendationCard */ false);
@@ -633,6 +654,8 @@
mSmartspaceMediaItemsCount = uiComponentIndex;
// Set up long press to show guts setting panel.
mRecommendationViewHolder.getDismiss().setOnClickListener(v -> {
+ if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
+
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
/* isRecommendationCard */ true);
closeGuts();
@@ -788,6 +811,8 @@
}
view.setOnClickListener(v -> {
+ if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
+
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
/* isRecommendationCard */ true,
interactedSubcardRank,
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
index f17ad6f..33ef19a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
@@ -50,6 +50,7 @@
holder.seekBar.setProgress(0)
holder.elapsedTimeView.setText("")
holder.totalTimeView.setText("")
+ holder.seekBar.contentDescription = ""
return
}
@@ -61,16 +62,22 @@
setVerticalPadding(seekBarEnabledVerticalPadding)
}
- data.duration?.let {
- holder.seekBar.setMax(it)
- holder.totalTimeView.setText(DateUtils.formatElapsedTime(
- it / DateUtils.SECOND_IN_MILLIS))
- }
+ holder.seekBar.setMax(data.duration)
+ val totalTimeString = DateUtils.formatElapsedTime(
+ data.duration / DateUtils.SECOND_IN_MILLIS)
+ holder.totalTimeView.setText(totalTimeString)
data.elapsedTime?.let {
holder.seekBar.setProgress(it)
- holder.elapsedTimeView.setText(DateUtils.formatElapsedTime(
- it / DateUtils.SECOND_IN_MILLIS))
+ val elapsedTimeString = DateUtils.formatElapsedTime(
+ it / DateUtils.SECOND_IN_MILLIS)
+ holder.elapsedTimeView.setText(elapsedTimeString)
+
+ holder.seekBar.contentDescription = holder.seekBar.context.getString(
+ R.string.controls_media_seekbar_description,
+ elapsedTimeString,
+ totalTimeString
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index d1b6548..125b87b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -135,14 +135,11 @@
if (currentlyConnected && mController.isActiveRemoteDevice(device)
&& mController.getSelectableMediaDevice().size() > 0) {
// Init active device layout
- mDivider.setVisibility(View.VISIBLE);
- mDivider.setTransitionAlpha(1);
mAddIcon.setVisibility(View.VISIBLE);
mAddIcon.setTransitionAlpha(1);
mAddIcon.setOnClickListener(this::onEndItemClick);
} else {
// Init non-active device layout
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
}
if (mCurrentActivePosition == position) {
@@ -181,7 +178,6 @@
super.onBind(customizedItem, topMargin, bottomMargin);
if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
mCheckBox.setVisibility(View.GONE);
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
mBottomDivider.setVisibility(View.GONE);
setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new),
@@ -196,13 +192,10 @@
mBottomDivider.setVisibility(View.GONE);
mCheckBox.setVisibility(View.GONE);
if (mController.getSelectableMediaDevice().size() > 0) {
- mDivider.setVisibility(View.VISIBLE);
- mDivider.setTransitionAlpha(1);
mAddIcon.setVisibility(View.VISIBLE);
mAddIcon.setTransitionAlpha(1);
mAddIcon.setOnClickListener(this::onEndItemClick);
} else {
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
}
mTitleIcon.setImageDrawable(getSpeakerDrawable());
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 0890841..1ffc2c4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -121,7 +121,6 @@
final ProgressBar mProgressBar;
final SeekBar mSeekBar;
final RelativeLayout mTwoLineLayout;
- final View mDivider;
final View mBottomDivider;
final CheckBox mCheckBox;
private String mDeviceId;
@@ -136,7 +135,6 @@
mTitleIcon = view.requireViewById(R.id.title_icon);
mProgressBar = view.requireViewById(R.id.volume_indeterminate_progress);
mSeekBar = view.requireViewById(R.id.volume_seekbar);
- mDivider = view.requireViewById(R.id.end_divider);
mBottomDivider = view.requireViewById(R.id.bottom_divider);
mAddIcon = view.requireViewById(R.id.add_icon);
mCheckBox = view.requireViewById(R.id.check_box);
@@ -151,21 +149,12 @@
return;
}
mTitleIcon.setImageIcon(icon);
- setMargin(topMargin, bottomMargin);
});
});
}
void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
- setMargin(topMargin, bottomMargin);
- }
-
- private void setMargin(boolean topMargin, boolean bottomMargin) {
- ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mContainerLayout
- .getLayoutParams();
- params.topMargin = topMargin ? mMargin : 0;
- params.bottomMargin = bottomMargin ? mMargin : 0;
- mContainerLayout.setLayoutParams(params);
+ // TODO (b/201718621): clean up method after adjustment
}
void setSingleLineLayout(CharSequence title, boolean bFocused) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 85d0802..6895ef1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -175,7 +175,7 @@
}
if (!mAdapter.isDragging() && !mAdapter.isAnimating()) {
int currentActivePosition = mAdapter.getCurrentActivePosition();
- if (currentActivePosition >= 0) {
+ if (currentActivePosition >= 0 && currentActivePosition < mAdapter.getItemCount()) {
mAdapter.notifyItemChanged(currentActivePosition);
} else {
mAdapter.notifyDataSetChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 437a0c8..42dd886 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -73,6 +73,7 @@
private final String mPackageName;
private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
+ private final LocalBluetoothManager mLocalBluetoothManager;
private final ShadeController mShadeController;
private final ActivityStarter mActivityStarter;
private final DialogLaunchAnimator mDialogLaunchAnimator;
@@ -85,7 +86,6 @@
private MediaController mMediaController;
@VisibleForTesting
Callback mCallback;
- Callback mPreviousCallback;
@VisibleForTesting
LocalMediaManager mLocalMediaManager;
@@ -101,6 +101,7 @@
mContext = context;
mPackageName = packageName;
mMediaSessionManager = mediaSessionManager;
+ mLocalBluetoothManager = lbm;
mShadeController = shadeController;
mActivityStarter = starter;
mAboveStatusbar = aboveStatusbar;
@@ -135,19 +136,7 @@
}
return;
}
-
- if (mPreviousCallback != null) {
- Log.w(TAG,
- "Callback started when mPreviousCallback is not null, which is unexpected");
- mPreviousCallback.dismissDialog();
- }
-
- // If we start the output group dialog when the output dialog is shown, we need to keep a
- // reference to the output dialog to set it back as the callback once we dismiss the output
- // group dialog.
- mPreviousCallback = mCallback;
mCallback = cb;
-
mLocalMediaManager.unregisterCallback(this);
mLocalMediaManager.stopScan();
mLocalMediaManager.registerCallback(this);
@@ -163,15 +152,6 @@
mLocalMediaManager.stopScan();
}
mMediaDevices.clear();
-
- // If there was a previous callback, i.e. we just dismissed the output group dialog and are
- // now back on the output dialog, then we reset the callback to its previous value.
- mCallback = null;
- Callback previous = mPreviousCallback;
- mPreviousCallback = null;
- if (previous != null) {
- start(previous);
- }
}
@Override
@@ -480,7 +460,11 @@
void launchMediaOutputGroupDialog(View mediaOutputDialog) {
// We show the output group dialog from the output dialog.
- MediaOutputGroupDialog dialog = new MediaOutputGroupDialog(mContext, mAboveStatusbar, this);
+ MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
+ mAboveStatusbar, mMediaSessionManager, mLocalBluetoothManager, mShadeController,
+ mActivityStarter, mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
+ MediaOutputGroupDialog dialog = new MediaOutputGroupDialog(mContext, mAboveStatusbar,
+ controller);
mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
index 968c350..11d76db 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
@@ -96,7 +96,6 @@
@Override
void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
super.onBind(device, topMargin, bottomMargin, position);
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
mBottomDivider.setVisibility(View.GONE);
mCheckBox.setVisibility(View.VISIBLE);
@@ -135,7 +134,6 @@
mTitleIcon.setImageDrawable(getSpeakerDrawable());
mBottomDivider.setVisibility(View.VISIBLE);
mCheckBox.setVisibility(View.GONE);
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
initSessionSeekbar();
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 7622d66..2badbfa 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -61,6 +61,7 @@
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -160,7 +161,8 @@
NavigationBarA11yHelper navigationBarA11yHelper,
TaskbarDelegate taskbarDelegate,
UserTracker userTracker,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ AutoHideController autoHideController) {
mContext = context;
mWindowManager = windowManager;
mAssistManagerLazy = assistManagerLazy;
@@ -194,9 +196,9 @@
mNavMode = mNavigationModeController.addListener(this);
mNavigationModeController.addListener(this);
mTaskbarDelegate = taskbarDelegate;
- mTaskbarDelegate.setOverviewProxyService(commandQueue, overviewProxyService,
+ mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
navigationBarA11yHelper, navigationModeController, sysUiFlagsContainer,
- dumpManager);
+ dumpManager, autoHideController);
mIsTablet = isTablet(mContext);
mUserTracker = userTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
index 0603bb7..73a0c54 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
@@ -118,7 +118,7 @@
configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
if (DEBUG) {
Log.d(TAG, "onOverlayChanged");
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 4d29612..d707dbd 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -19,6 +19,8 @@
import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.containsType;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -57,7 +59,9 @@
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.AutoHideController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -77,6 +81,7 @@
private NavigationBarA11yHelper mNavigationBarA11yHelper;
private NavigationModeController mNavigationModeController;
private SysUiState mSysUiState;
+ private AutoHideController mAutoHideController;
private int mDisplayId;
private int mNavigationIconHints;
private final NavigationBarA11yHelper.NavA11yEventListener mNavA11yEventListener =
@@ -87,6 +92,28 @@
private final Context mContext;
private final DisplayManager mDisplayManager;
private Context mWindowContext;
+ /**
+ * Tracks the system calls for when taskbar should transiently show or hide so we can return
+ * this value in {@link AutoHideUiElement#isVisible()} below.
+ *
+ * This also gets set by {@link #onTaskbarAutohideSuspend(boolean)} to force show the transient
+ * taskbar if launcher has requested to suspend auto-hide behavior.
+ */
+ private boolean mTaskbarTransientShowing;
+ private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() {
+ @Override
+ public void synchronizeState() {
+ }
+
+ @Override
+ public boolean isVisible() {
+ return mTaskbarTransientShowing;
+ }
+
+ @Override
+ public void hide() {
+ }
+ };
@Inject
public TaskbarDelegate(Context context) {
@@ -96,11 +123,12 @@
mDisplayManager = mContext.getSystemService(DisplayManager.class);
}
- public void setOverviewProxyService(CommandQueue commandQueue,
+ public void setDependencies(CommandQueue commandQueue,
OverviewProxyService overviewProxyService,
NavigationBarA11yHelper navigationBarA11yHelper,
NavigationModeController navigationModeController,
- SysUiState sysUiState, DumpManager dumpManager) {
+ SysUiState sysUiState, DumpManager dumpManager,
+ AutoHideController autoHideController) {
// TODO: adding this in the ctor results in a dagger dependency cycle :(
mCommandQueue = commandQueue;
mOverviewProxyService = overviewProxyService;
@@ -108,18 +136,7 @@
mNavigationModeController = navigationModeController;
mSysUiState = sysUiState;
dumpManager.registerDumpable(this);
- }
-
- public void destroy() {
- mCommandQueue.removeCallback(this);
- mOverviewProxyService.removeCallback(this);
- mNavigationModeController.removeListener(this);
- mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener);
- mEdgeBackGestureHandler.onNavBarDetached();
- if (mWindowContext != null) {
- mWindowContext.unregisterComponentCallbacks(this);
- mWindowContext = null;
- }
+ mAutoHideController = autoHideController;
}
public void init(int displayId) {
@@ -136,6 +153,20 @@
mWindowContext.registerComponentCallbacks(this);
// Set initial state for any listeners
updateSysuiFlags();
+ mAutoHideController.setNavigationBar(mAutoHideUiElement);
+ }
+
+ public void destroy() {
+ mCommandQueue.removeCallback(this);
+ mOverviewProxyService.removeCallback(this);
+ mNavigationModeController.removeListener(this);
+ mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener);
+ mEdgeBackGestureHandler.onNavBarDetached();
+ if (mWindowContext != null) {
+ mWindowContext.unregisterComponentCallbacks(this);
+ mWindowContext = null;
+ }
+ mAutoHideController.setNavigationBar(null);
}
private void updateSysuiFlags() {
@@ -209,6 +240,38 @@
}
@Override
+ public void showTransient(int displayId, int[] types) {
+ if (displayId != mDisplayId) {
+ return;
+ }
+ if (!containsType(types, ITYPE_NAVIGATION_BAR)) {
+ return;
+ }
+ mTaskbarTransientShowing = true;
+ }
+
+ @Override
+ public void abortTransient(int displayId, int[] types) {
+ if (displayId != mDisplayId) {
+ return;
+ }
+ if (!containsType(types, ITYPE_NAVIGATION_BAR)) {
+ return;
+ }
+ mTaskbarTransientShowing = false;
+ }
+
+ @Override
+ public void onTaskbarAutohideSuspend(boolean suspend) {
+ mTaskbarTransientShowing = suspend;
+ if (suspend) {
+ mAutoHideController.suspendAutoHide();
+ } else {
+ mAutoHideController.resumeSuspendedAutoHide();
+ }
+ }
+
+ @Override
public void onNavigationModeChanged(int mode) {
mEdgeBackGestureHandler.onNavigationModeChanged(mode);
}
@@ -236,6 +299,7 @@
pw.println(" mDisabledFlags=" + mDisabledFlags);
pw.println(" mTaskBarWindowState=" + mTaskBarWindowState);
pw.println(" mBehavior=" + mBehavior);
+ pw.println(" mTaskbarTransientShowing=" + mTaskbarTransientShowing);
mEdgeBackGestureHandler.dump(pw);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index bfb63ea..90d3448 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -35,7 +35,6 @@
import com.android.systemui.qs.TouchAnimator.Listener;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.tileimpl.HeightOverrideable;
-import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.wm.shell.animation.Interpolators;
@@ -170,19 +169,6 @@
}
}
- void startAlphaAnimation(boolean show) {
- if (show == mToShowing) {
- return;
- }
- mToShowing = show;
- if (show) {
- CrossFadeHelper.fadeIn(mQs.getView(), QQS_FADE_IN_DURATION, 0 /* delay */);
- } else {
- CrossFadeHelper.fadeOut(mQs.getView(), QQS_FADE_OUT_DURATION, 0 /* delay */,
- null /* endRunnable */);
- }
- }
-
/**
* Sets whether or not the keyguard is currently being shown with a collapsed header.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index b8376da..f300efc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -89,6 +89,7 @@
private int mLayoutDirection;
private QSFooter mFooter;
private float mLastQSExpansion = -1;
+ private float mLastPanelFraction;
private boolean mQsDisabled;
private ImageView mQsDragHandler;
@@ -120,7 +121,7 @@
* When true, QS will translate from outside the screen. It will be clipped with parallax
* otherwise.
*/
- private boolean mTranslateWhileExpanding;
+ private boolean mInSplitShade;
private boolean mPulseExpanding;
/**
@@ -135,6 +136,12 @@
private DumpManager mDumpManager;
+ /**
+ * Progress of pull down from the center of the lock screen.
+ * @see com.android.systemui.statusbar.LockscreenShadeTransitionController
+ */
+ private float mFullShadeProgress;
+
@Inject
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
InjectionInflationController injectionInflater, QSTileHost qsTileHost,
@@ -226,7 +233,8 @@
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
boolean sizeChanged = (oldTop - oldBottom) != (top - bottom);
if (sizeChanged) {
- setQsExpansion(mLastQSExpansion, mLastHeaderTranslation);
+ setQsExpansion(mLastQSExpansion, mLastPanelFraction,
+ mLastHeaderTranslation);
}
});
mQSPanelController.setUsingHorizontalLayoutChangeListener(
@@ -408,7 +416,7 @@
mQSAnimator.setShowCollapsedOnKeyguard(showCollapsed);
}
if (!showCollapsed && isKeyguardState()) {
- setQsExpansion(mLastQSExpansion, 0);
+ setQsExpansion(mLastQSExpansion, mLastPanelFraction, 0);
}
}
}
@@ -476,33 +484,30 @@
}
@Override
- public void setTranslateWhileExpanding(boolean shouldTranslate) {
- mTranslateWhileExpanding = shouldTranslate;
- mQSAnimator.setTranslateWhileExpanding(shouldTranslate);
+ public void setInSplitShade(boolean inSplitShade) {
+ mInSplitShade = inSplitShade;
+ mQSAnimator.setTranslateWhileExpanding(inSplitShade);
}
@Override
- public void setTransitionToFullShadeAmount(float pxAmount, boolean animated) {
+ public void setTransitionToFullShadeAmount(float pxAmount, float progress) {
boolean isTransitioningToFullShade = pxAmount > 0;
if (isTransitioningToFullShade != mTransitioningToFullShade) {
mTransitioningToFullShade = isTransitioningToFullShade;
updateShowCollapsedOnKeyguard();
- setQsExpansion(mLastQSExpansion, mLastHeaderTranslation);
}
+ mFullShadeProgress = progress;
+ setQsExpansion(mLastQSExpansion, mLastPanelFraction, mLastHeaderTranslation);
}
@Override
- public void setQsExpansion(float expansion, float proposedTranslation) {
- if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + proposedTranslation);
+ public void setQsExpansion(float expansion, float panelExpansionFraction,
+ float proposedTranslation) {
float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation;
- if (mQSAnimator != null) {
- final boolean showQSOnLockscreen = expansion > 0;
- final boolean showQSUnlocked = headerTranslation == 0 || !mTranslateWhileExpanding;
- mQSAnimator.startAlphaAnimation(showQSOnLockscreen || showQSUnlocked
- || mTransitioningToFullShade);
- }
+ float progress = mTransitioningToFullShade ? mFullShadeProgress : panelExpansionFraction;
+ setAlphaAnimationProgress(mInSplitShade ? progress : 1);
mContainer.setExpansion(expansion);
- final float translationScaleY = (mTranslateWhileExpanding
+ final float translationScaleY = (mInSplitShade
? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1);
boolean onKeyguardAndExpanded = isKeyguardState() && !mShowCollapsedOnKeyguard;
if (!mHeaderAnimating && !headerWillBeAnimating()) {
@@ -519,6 +524,7 @@
return;
}
mLastHeaderTranslation = headerTranslation;
+ mLastPanelFraction = panelExpansionFraction;
mLastQSExpansion = expansion;
mLastKeyguardAndExpanded = onKeyguardAndExpanded;
mLastViewHeight = currentHeight;
@@ -560,6 +566,17 @@
updateMediaPositions();
}
+ private void setAlphaAnimationProgress(float progress) {
+ final View view = getView();
+ if (progress == 0 && view.getVisibility() != View.INVISIBLE) {
+ view.setVisibility(View.INVISIBLE);
+ } else if (progress > 0 && view.getVisibility() != View.VISIBLE) {
+ view.setVisibility((View.VISIBLE));
+ }
+ float alpha = Interpolators.getNotificationScrimAlpha(progress, true /* uiContent */);
+ view.setAlpha(alpha);
+ }
+
private void updateQsBounds() {
if (mLastQSExpansion == 1.0f) {
// Fully expanded, let's set the layout bounds as clip bounds. This is necessary because
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
index 2ad06c1..01afa56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
@@ -32,7 +32,7 @@
*/
class UserDialog(
context: Context
-) : SystemUIDialog(context, R.style.Theme_SystemUI_Dialog_QSDialog) {
+) : SystemUIDialog(context) {
// create() is no-op after creation
private lateinit var _doneButton: View
@@ -72,7 +72,7 @@
attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
attributes.receiveInsetsIgnoringZOrder = true
setLayout(
- context.resources.getDimensionPixelSize(R.dimen.qs_panel_width),
+ context.resources.getDimensionPixelSize(R.dimen.notification_panel_width),
ViewGroup.LayoutParams.WRAP_CONTENT
)
setGravity(Gravity.CENTER)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index a5e4ba1..bae7996 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -21,6 +21,7 @@
import android.provider.Settings
import android.view.View
import androidx.annotation.VisibleForTesting
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -36,6 +37,7 @@
private val userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
private val activityStarter: ActivityStarter,
private val falsingManager: FalsingManager,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
private val dialogFactory: (Context) -> UserDialog
) {
@@ -43,11 +45,13 @@
constructor(
userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
activityStarter: ActivityStarter,
- falsingManager: FalsingManager
+ falsingManager: FalsingManager,
+ dialogLaunchAnimator: DialogLaunchAnimator
) : this(
userDetailViewAdapterProvider,
activityStarter,
falsingManager,
+ dialogLaunchAnimator,
{ UserDialog(it) }
)
@@ -69,7 +73,11 @@
settingsButton.setOnClickListener {
if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- activityStarter.postStartActivityDismissingKeyguard(USER_SETTINGS_INTENT, 0)
+ dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
+ activityStarter.postStartActivityDismissingKeyguard(
+ USER_SETTINGS_INTENT,
+ 0
+ )
}
dismiss()
}
@@ -81,7 +89,7 @@
}
adapter.linkToViewGroup(grid)
- show()
+ dialogLaunchAnimator.showFromView(this, view)
}
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index c76f01b..721a6af 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -248,6 +248,12 @@
onTaskbarStatusUpdated(visible, stashed));
}
+ @Override
+ public void notifyTaskbarAutohideSuspend(boolean suspend) {
+ verifyCallerAndClearCallingIdentityPostMain("notifyTaskbarAutohideSuspend", () ->
+ onTaskbarAutohideSuspend(suspend));
+ }
+
private boolean sendEvent(int action, int code) {
long when = SystemClock.uptimeMillis();
final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
@@ -818,6 +824,12 @@
}
}
+ private void onTaskbarAutohideSuspend(boolean suspend) {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).onTaskbarAutohideSuspend(suspend);
+ }
+ }
+
private void notifyConnectionChanged() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null);
@@ -1000,6 +1012,7 @@
default void onNavBarButtonAlphaChanged(float alpha, boolean animate) {}
default void onHomeRotationEnabled(boolean enabled) {}
default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {}
+ default void onTaskbarAutohideSuspend(boolean suspend) {}
default void onSystemUiStateChanged(int sysuiStateFlags) {}
default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
default void onAssistantGestureCompletion(float velocity) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 8a39719..74ebfe5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -147,7 +147,6 @@
private boolean mBatteryPresent = true;
private long mChargingTimeRemaining;
private String mMessageToShowOnScreenOn;
- protected int mLockScreenMode;
private boolean mInited;
private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
@@ -600,10 +599,6 @@
mHideTransientMessageOnScreenOff = hideOnScreenOff && transientIndication != null;
mHandler.removeMessages(MSG_HIDE_TRANSIENT);
mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
- if (mDozing && !TextUtils.isEmpty(mTransientIndication)) {
- // Make sure this doesn't get stuck and burns in. Acquire wakelock until its cleared.
- mWakeLock.setAcquired(true);
- }
hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
updateIndication(false);
@@ -623,10 +618,6 @@
}
protected final void updateIndication(boolean animate) {
- if (TextUtils.isEmpty(mTransientIndication)) {
- mWakeLock.setAcquired(false);
- }
-
if (!mVisible) {
return;
}
@@ -644,24 +635,31 @@
// colors can be hard to read in low brightness.
mTopIndicationView.setTextColor(Color.WHITE);
if (!TextUtils.isEmpty(mTransientIndication)) {
- mTopIndicationView.switchIndication(mTransientIndication, null);
+ mWakeLock.setAcquired(true);
+ mTopIndicationView.switchIndication(mTransientIndication, null,
+ true, () -> mWakeLock.setAcquired(false));
} else if (!mBatteryPresent) {
// If there is no battery detected, hide the indication and bail
mIndicationArea.setVisibility(GONE);
} else if (!TextUtils.isEmpty(mAlignmentIndication)) {
- mTopIndicationView.switchIndication(mAlignmentIndication, null);
+ mTopIndicationView.switchIndication(mAlignmentIndication, null,
+ false /* animate */, null /* onAnimationEndCallback */);
mTopIndicationView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
} else if (mPowerPluggedIn || mEnableBatteryDefender) {
String indication = computePowerIndication();
if (animate) {
- animateText(mTopIndicationView, indication);
+ mWakeLock.setAcquired(true);
+ mTopIndicationView.switchIndication(indication, null, true /* animate */,
+ () -> mWakeLock.setAcquired(false));
} else {
- mTopIndicationView.switchIndication(indication, null);
+ mTopIndicationView.switchIndication(indication, null, false /* animate */,
+ null /* onAnimationEndCallback */);
}
} else {
String percentage = NumberFormat.getPercentInstance()
.format(mBatteryLevel / 100f);
- mTopIndicationView.switchIndication(percentage, null);
+ mTopIndicationView.switchIndication(percentage, null /* indication */,
+ false /* animate */, null /* onAnimationEnd*/);
}
return;
}
@@ -819,12 +817,15 @@
}
}
- private void showTryFingerprintMsg(String a11yString) {
+ private void showTryFingerprintMsg(int msgId, String a11yString) {
if (mKeyguardUpdateMonitor.isUdfpsAvailable()) {
// if udfps available, there will always be a tappable affordance to unlock
// For example, the lock icon
if (mKeyguardBypassController.getUserHasDeviceEntryIntent()) {
showTransientIndication(R.string.keyguard_unlock_press);
+ } else if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
+ // since face is locked out, simply show "try fingerprint"
+ showTransientIndication(R.string.keyguard_try_fingerprint);
} else {
showTransientIndication(R.string.keyguard_face_failed_use_fp);
}
@@ -860,11 +861,6 @@
public static final int HIDE_DELAY_MS = 5000;
@Override
- public void onLockScreenModeChanged(int mode) {
- mLockScreenMode = mode;
- }
-
- @Override
public void onRefreshBatteryInfo(BatteryStatus status) {
boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
|| status.status == BatteryManager.BATTERY_STATUS_FULL;
@@ -916,7 +912,7 @@
} else if (mKeyguardUpdateMonitor.isScreenOn()) {
if (biometricSourceType == BiometricSourceType.FACE
&& shouldSuppressFaceMsgAndShowTryFingerprintMsg()) {
- showTryFingerprintMsg(helpString);
+ showTryFingerprintMsg(msgId, helpString);
return;
}
showTransientIndication(helpString, false /* isError */, showActionToUnlock);
@@ -936,7 +932,7 @@
&& shouldSuppressFaceMsgAndShowTryFingerprintMsg()
&& !mStatusBarKeyguardViewManager.isBouncerShowing()
&& mKeyguardUpdateMonitor.isScreenOn()) {
- showTryFingerprintMsg(errString);
+ showTryFingerprintMsg(msgId, errString);
return;
}
if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
@@ -945,7 +941,7 @@
if (!mStatusBarKeyguardViewManager.isBouncerShowing()
&& mKeyguardUpdateMonitor.isUdfpsEnrolled()
&& mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
- showTryFingerprintMsg(errString);
+ showTryFingerprintMsg(msgId, errString);
} else if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
mStatusBarKeyguardViewManager.showBouncerMessage(
mContext.getResources().getString(R.string.keyguard_unlock_press),
@@ -989,8 +985,8 @@
private boolean shouldSuppressFaceMsgAndShowTryFingerprintMsg() {
// For dual biometric, don't show face auth messages
return mKeyguardUpdateMonitor.isFingerprintDetectionRunning()
- && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
- true /* isStrongBiometric */);
+ && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ true /* isStrongBiometric */);
}
private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 03d8e7e..77e329f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -208,6 +208,11 @@
lateinit var isScrimOpaqueChangedListener: Consumer<Boolean>
/**
+ * A runnable to call when the scrim has been fully revealed. This is only invoked once
+ */
+ var fullyRevealedRunnable: Runnable? = null
+
+ /**
* How much of the underlying views are revealed, in percent. 0 means they will be completely
* obscured and 1 means they'll be fully visible.
*/
@@ -218,10 +223,20 @@
revealEffect.setRevealAmountOnScrim(value, this)
updateScrimOpaque()
+ maybeTriggerFullyRevealedRunnable()
invalidate()
}
}
+ private fun maybeTriggerFullyRevealedRunnable() {
+ if (revealAmount == 1.0f) {
+ fullyRevealedRunnable?.let {
+ it.run()
+ fullyRevealedRunnable = null
+ }
+ }
+ }
+
/**
* The [LightRevealEffect] used to manipulate the radial gradient whenever [revealAmount]
* changes.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index ca18b07..dca7f70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -298,9 +298,8 @@
nsslController.setTransitionToFullShadeAmount(field)
notificationPanelController.setTransitionToFullShadeAmount(field,
false /* animate */, 0 /* delay */)
- // TODO: appear qs also in split shade
- val qsAmount = if (useSplitShade) 0f else field
- qS.setTransitionToFullShadeAmount(qsAmount, false /* animate */)
+ val progress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
+ qS.setTransitionToFullShadeAmount(field, progress)
// TODO: appear media also in split shade
val mediaAmount = if (useSplitShade) 0f else field
mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 7aa2dc7..d4f54e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -36,7 +36,6 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
-import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.PanelExpansionListener
import com.android.systemui.statusbar.phone.ScrimController
@@ -73,6 +72,10 @@
private const val TAG = "DepthController"
}
+ /**
+ * Did we already unblur while dozing?
+ */
+ private var alreadyUnblurredWhileDozing = false
lateinit var root: View
private var blurRoot: View? = null
private var keyguardAnimator: Animator? = null
@@ -229,9 +232,11 @@
private val keyguardStateCallback = object : KeyguardStateController.Callback {
override fun onKeyguardFadingAwayChanged() {
if (!keyguardStateController.isKeyguardFadingAway ||
- biometricUnlockController.mode != MODE_WAKE_AND_UNLOCK) {
+ !biometricUnlockController.isWakeAndUnlock) {
return
}
+ // When wakeAndUnlocking the screen remains dozing, so we have to manually trigger
+ // the unblur earlier
keyguardAnimator?.cancel()
keyguardAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
@@ -253,6 +258,7 @@
})
start()
}
+ alreadyUnblurredWhileDozing = statusBarStateController.dozeAmount != 0.0f
}
override fun onKeyguardShowingChanged() {
@@ -274,10 +280,24 @@
if (isDozing) {
shadeAnimation.finishIfRunning()
brightnessMirrorSpring.finishIfRunning()
+
+ // unset this for safety, to be ready for the next wakeup
+ alreadyUnblurredWhileDozing = false
}
}
override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ if (alreadyUnblurredWhileDozing) {
+ if (linear == 0.0f) {
+ // We finished waking up, let's reset
+ alreadyUnblurredWhileDozing = false
+ } else {
+ // We've already handled the unbluring from the keyguardAnimator above.
+ // if we would continue, we'd play another unzoom / blur animation from the
+ // dozing changing.
+ return
+ }
+ }
wakeAndUnlockBlurRadius = blurUtils.blurRadiusOfRatio(eased)
scheduleUpdate()
}
@@ -435,6 +455,7 @@
it.println("blursDisabledForAppLaunch: $blursDisabledForAppLaunch")
it.println("qsPanelExpansion: $qsPanelExpansion")
it.println("transitionToFullShadeProgress: $transitionToFullShadeProgress")
+ it.println("alreadyUnblurredWhileDozing: $alreadyUnblurredWhileDozing")
it.println("lastAppliedBlur: $lastAppliedBlur")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index cbb3aba..6da981b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -28,6 +28,7 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.os.SystemProperties;
+import android.os.Trace;
import android.text.format.DateFormat;
import android.util.FloatProperty;
import android.util.Log;
@@ -181,6 +182,7 @@
}
synchronized (mListeners) {
+ Trace.beginSection(TAG + "#setState(" + StatusBarState.toShortString(state) + ")");
String tag = getClass().getSimpleName() + "#setState(" + state + ")";
DejankUtils.startDetectingBlockingIpcs(tag);
for (RankedListener rl : new ArrayList<>(mListeners)) {
@@ -198,6 +200,7 @@
rl.mListener.onStatePostChange();
}
DejankUtils.stopDetectingBlockingIpcs(tag);
+ Trace.endSection();
}
return true;
@@ -262,12 +265,14 @@
mIsDozing = isDozing;
synchronized (mListeners) {
+ Trace.beginSection(TAG + "#setDozing(" + isDozing + ")");
String tag = getClass().getSimpleName() + "#setIsDozing";
DejankUtils.startDetectingBlockingIpcs(tag);
for (RankedListener rl : new ArrayList<>(mListeners)) {
rl.mListener.onDozingChanged(isDozing);
}
DejankUtils.stopDetectingBlockingIpcs(tag);
+ Trace.endSection();
}
return true;
@@ -333,12 +338,14 @@
mDozeAmount = dozeAmount;
float interpolatedAmount = mDozeInterpolator.getInterpolation(dozeAmount);
synchronized (mListeners) {
+ Trace.beginSection(TAG + "#setDozeAmount");
String tag = getClass().getSimpleName() + "#setDozeAmount";
DejankUtils.startDetectingBlockingIpcs(tag);
for (RankedListener rl : new ArrayList<>(mListeners)) {
rl.mListener.onDozeAmountChanged(mDozeAmount, interpolatedAmount);
}
DejankUtils.stopDetectingBlockingIpcs(tag);
+ Trace.endSection();
}
}
@@ -469,11 +476,13 @@
public void setPulsing(boolean pulsing) {
if (mPulsing != pulsing) {
mPulsing = pulsing;
+ Trace.beginSection(TAG + "#setPulsing(" + pulsing + ")");
synchronized (mListeners) {
for (RankedListener rl : new ArrayList<>(mListeners)) {
rl.mListener.onPulsingChanged(pulsing);
}
}
+ Trace.endSection();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
index d74297e..04c60fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
@@ -114,9 +114,6 @@
override fun onThemeChanged() {
updateRippleColor()
}
- override fun onOverlayChanged() {
- updateRippleColor()
- }
override fun onConfigChanged(newConfig: Configuration?) {
normalizedPortPosX = context.resources.getFloat(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index 0352212..d297d95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -47,6 +47,7 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.commandline.CommandRegistry;
+import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.DynamicChildBindController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -255,15 +256,30 @@
IActivityManager iActivityManager,
OngoingCallLogger logger,
DumpManager dumpManager,
- StatusBarWindowController statusBarWindowController) {
+ StatusBarWindowController statusBarWindowController,
+ SwipeStatusBarAwayGestureHandler swipeStatusBarAwayGestureHandler,
+ StatusBarStateController statusBarStateController) {
Optional<StatusBarWindowController> windowController =
featureFlags.isOngoingCallInImmersiveEnabled()
? Optional.of(statusBarWindowController)
: Optional.empty();
+ Optional<SwipeStatusBarAwayGestureHandler> gestureHandler =
+ featureFlags.isOngoingCallInImmersiveEnabled()
+ ? Optional.of(swipeStatusBarAwayGestureHandler)
+ : Optional.empty();
OngoingCallController ongoingCallController =
new OngoingCallController(
- notifCollection, featureFlags, systemClock, activityStarter, mainExecutor,
- iActivityManager, logger, dumpManager, windowController);
+ notifCollection,
+ featureFlags,
+ systemClock,
+ activityStarter,
+ mainExecutor,
+ iActivityManager,
+ logger,
+ dumpManager,
+ windowController,
+ gestureHandler,
+ statusBarStateController);
ongoingCallController.init();
return ongoingCallController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
new file mode 100644
index 0000000..80577ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.systemui.statusbar.gesture
+
+import android.content.Context
+import android.os.Looper
+import android.view.Choreographer
+import android.view.Display
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.MotionEvent.*
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.system.InputChannelCompat
+import com.android.systemui.shared.system.InputMonitorCompat
+import com.android.systemui.statusbar.phone.StatusBarWindowController
+import javax.inject.Inject
+
+/**
+ * A class to detect when a user swipes away the status bar. To be notified when the swipe away
+ * gesture is detected, add a callback via [addOnGestureDetectedCallback].
+ */
+@SysUISingleton
+open class SwipeStatusBarAwayGestureHandler @Inject constructor(
+ context: Context,
+ private val statusBarWindowController: StatusBarWindowController,
+ private val logger: SwipeStatusBarAwayGestureLogger
+) {
+
+ /**
+ * Active callbacks, each associated with a tag. Gestures will only be monitored if
+ * [callbacks.size] > 0.
+ */
+ private val callbacks: MutableMap<String, () -> Unit> = mutableMapOf()
+
+ private var startY: Float = 0f
+ private var startTime: Long = 0L
+ private var monitoringCurrentTouch: Boolean = false
+
+ private var inputMonitor: InputMonitorCompat? = null
+ private var inputReceiver: InputChannelCompat.InputEventReceiver? = null
+
+ private var swipeDistanceThreshold: Int = context.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.system_gestures_start_threshold
+ )
+
+ /** Adds a callback that will be triggered when the swipe away gesture is detected. */
+ fun addOnGestureDetectedCallback(tag: String, callback: () -> Unit) {
+ val callbacksWasEmpty = callbacks.isEmpty()
+ callbacks[tag] = callback
+ if (callbacksWasEmpty) {
+ startGestureListening()
+ }
+ }
+
+ /** Removes the callback. */
+ fun removeOnGestureDetectedCallback(tag: String) {
+ callbacks.remove(tag)
+ if (callbacks.isEmpty()) {
+ stopGestureListening()
+ }
+ }
+
+ private fun onInputEvent(ev: InputEvent) {
+ if (ev !is MotionEvent) {
+ return
+ }
+
+ when (ev.actionMasked) {
+ ACTION_DOWN -> {
+ if (
+ // Gesture starts just below the status bar
+ ev.y >= statusBarWindowController.statusBarHeight
+ && ev.y <= 3 * statusBarWindowController.statusBarHeight
+ ) {
+ logger.logGestureDetectionStarted(ev.y.toInt())
+ startY = ev.y
+ startTime = ev.eventTime
+ monitoringCurrentTouch = true
+ } else {
+ monitoringCurrentTouch = false
+ }
+ }
+ ACTION_MOVE -> {
+ if (!monitoringCurrentTouch) {
+ return
+ }
+ if (
+ // Gesture is up
+ ev.y < startY
+ // Gesture went far enough
+ && (startY - ev.y) >= swipeDistanceThreshold
+ // Gesture completed quickly enough
+ && (ev.eventTime - startTime) < SWIPE_TIMEOUT_MS
+ ) {
+ monitoringCurrentTouch = false
+ logger.logGestureDetected(ev.y.toInt())
+ callbacks.values.forEach { it.invoke() }
+ }
+ }
+ ACTION_CANCEL, ACTION_UP -> {
+ if (monitoringCurrentTouch) {
+ logger.logGestureDetectionEndedWithoutTriggering(ev.y.toInt())
+ }
+ monitoringCurrentTouch = false
+ }
+ }
+ }
+
+ /** Start listening for the swipe gesture. */
+ private fun startGestureListening() {
+ stopGestureListening()
+
+ logger.logInputListeningStarted()
+ inputMonitor = InputMonitorCompat(TAG, Display.DEFAULT_DISPLAY).also {
+ inputReceiver = it.getInputReceiver(
+ Looper.getMainLooper(),
+ Choreographer.getInstance(),
+ this::onInputEvent
+ )
+ }
+ }
+
+ /** Stop listening for the swipe gesture. */
+ private fun stopGestureListening() {
+ inputMonitor?.let {
+ logger.logInputListeningStopped()
+ inputMonitor = null
+ it.dispose()
+ }
+ inputReceiver?.let {
+ inputReceiver = null
+ it.dispose()
+ }
+ }
+}
+
+private const val SWIPE_TIMEOUT_MS: Long = 500
+private val TAG = SwipeStatusBarAwayGestureHandler::class.simpleName
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
new file mode 100644
index 0000000..17feaa8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.systemui.statusbar.gesture
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.SwipeStatusBarAwayLog
+import javax.inject.Inject
+
+/** Log messages for [SwipeStatusBarAwayGestureHandler]. */
+class SwipeStatusBarAwayGestureLogger @Inject constructor(
+ @SwipeStatusBarAwayLog private val buffer: LogBuffer
+) {
+ fun logGestureDetectionStarted(y: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = y },
+ { "Beginning gesture detection. y=$int1" }
+ )
+ }
+
+ fun logGestureDetectionEndedWithoutTriggering(y: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = y },
+ { "Gesture finished; no swipe up gesture detected. Final y=$int1" }
+ )
+ }
+
+ fun logGestureDetected(y: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { int1 = y },
+ { "Gesture detected; notifying callbacks. y=$int1" }
+ )
+ }
+
+ fun logInputListeningStarted() {
+ buffer.log(TAG, LogLevel.VERBOSE, {}, { "Input listening started "})
+ }
+
+ fun logInputListeningStopped() {
+ buffer.log(TAG, LogLevel.VERBOSE, {}, { "Input listening stopped "})
+ }
+}
+
+private const val TAG = "SwipeStatusBarAwayGestureHandler"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index bdade2c..bacb85a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -29,6 +29,7 @@
import android.os.Handler
import android.os.UserHandle
import android.provider.Settings
+import android.util.Log
import android.view.View
import android.view.ViewGroup
import com.android.settingslib.Utils
@@ -73,6 +74,10 @@
@Main private val handler: Handler,
optionalPlugin: Optional<BcSmartspaceDataPlugin>
) {
+ companion object {
+ private const val TAG = "LockscreenSmartspaceController"
+ }
+
private var session: SmartspaceSession? = null
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
@@ -210,6 +215,7 @@
val newSession = smartspaceManager.createSmartspaceSession(
SmartspaceConfig.Builder(context, "lockscreen").build())
+ Log.d(TAG, "Starting smartspace session for lockscreen")
newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
@@ -231,6 +237,8 @@
* Disconnects the smartspace view from the smartspace service and cleans up any resources.
*/
fun disconnect() {
+ if (!smartspaceViews.isEmpty()) return
+
execution.assertIsMainThread()
if (session == null) {
@@ -248,6 +256,7 @@
session = null
plugin?.onTargetsAvailable(emptyList())
+ Log.d(TAG, "Ending smartspace session for lockscreen")
}
fun addListener(listener: SmartspaceTargetListener) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 9fe06a0..b226aec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -272,15 +272,6 @@
}
@Override
- public void onOverlayChanged() {
- updateShowEmptyShadeView();
- mView.updateCornerRadius();
- mView.updateBgColor();
- mView.updateDecorViews();
- mView.reinflateViews();
- }
-
- @Override
public void onUiModeChanged() {
mView.updateBgColor();
mView.updateDecorViews();
@@ -288,6 +279,11 @@
@Override
public void onThemeChanged() {
+ updateShowEmptyShadeView();
+ mView.updateCornerRadius();
+ mView.updateBgColor();
+ mView.updateDecorViews();
+ mView.reinflateViews();
updateFooter();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index aeb2efd..e12009e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -37,6 +37,7 @@
private final Handler mHandler;
private AutoHideUiElement mStatusBar;
+ /** For tablets, this will represent the Taskbar */
private AutoHideUiElement mNavigationBar;
private int mDisplayId;
@@ -89,7 +90,7 @@
}
}
- void resumeSuspendedAutoHide() {
+ public void resumeSuspendedAutoHide() {
if (mAutoHideSuspended) {
scheduleAutoHide();
Runnable checkBarModesRunnable = getCheckBarModesRunnable();
@@ -99,7 +100,7 @@
}
}
- void suspendAutoHide() {
+ public void suspendAutoHide() {
mHandler.removeCallbacks(mAutoHide);
Runnable checkBarModesRunnable = getCheckBarModesRunnable();
if (checkBarModesRunnable != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 12ae3f1..96fa8a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -123,7 +123,7 @@
if (lastConfig.updateFrom(newConfig) and ActivityInfo.CONFIG_ASSETS_PATHS != 0) {
listeners.filterForEach({ this.listeners.contains(it) }) {
- it.onOverlayChanged()
+ it.onThemeChanged()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 878fbbf..927b4c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -163,7 +163,6 @@
mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null);
mWakeUpCoordinator.removeListener(this);
mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
- mNotificationPanelViewController.setVerticalTranslationListener(null);
mNotificationPanelViewController.setHeadsUpAppearanceController(null);
mStackScrollerController.removeOnExpandedHeightChangedListener(mSetExpandedHeight);
mDarkIconDispatcher.removeDarkReceiver(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 4b545eb..5f402d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -121,7 +121,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
updateResources();
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index a5b5f1c..3a68b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -95,44 +95,85 @@
}
/**
- * Changes the text with an animation and makes sure a single indication is shown long enough.
+ * Changes the text with an animation. Makes sure a single indication is shown long enough.
+ */
+ public void switchIndication(CharSequence text, KeyguardIndication indication) {
+ switchIndication(text, indication, true, null);
+ }
+
+ /**
+ * Changes the text with an optional animation. For animating text, makes sure a single
+ * indication is shown long enough.
*
* @param text The text to show.
* @param indication optional display information for the text
+ * @param animate whether to animate this indication in - we may not want this on AOD
+ * @param onAnimationEndCallback runnable called after this indication is animated in
*/
- public void switchIndication(CharSequence text, KeyguardIndication indication) {
+ public void switchIndication(CharSequence text, KeyguardIndication indication,
+ boolean animate, Runnable onAnimationEndCallback) {
if (text == null) text = "";
CharSequence lastPendingMessage = mMessages.peekLast();
if (TextUtils.equals(lastPendingMessage, text)
|| (lastPendingMessage == null && TextUtils.equals(text, getText()))) {
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
return;
}
mMessages.add(text);
mKeyguardIndicationInfo.add(indication);
- final boolean hasIcon = indication != null && indication.getIcon() != null;
- final AnimatorSet animSet = new AnimatorSet();
- final AnimatorSet.Builder animSetBuilder = animSet.play(getOutAnimator());
+ if (animate) {
+ final boolean hasIcon = indication != null && indication.getIcon() != null;
+ final AnimatorSet animator = new AnimatorSet();
+ // Make sure each animation is visible for a minimum amount of time, while not worrying
+ // about fading in blank text
+ long timeInMillis = System.currentTimeMillis();
+ long delay = Math.max(0, mNextAnimationTime - timeInMillis);
+ setNextAnimationTime(timeInMillis + delay + getFadeOutDuration());
+ final long minDurationMillis =
+ (indication != null && indication.getMinVisibilityMillis() != null)
+ ? indication.getMinVisibilityMillis()
+ : MSG_MIN_DURATION_MILLIS_DEFAULT;
+ if (!text.equals("") || hasIcon) {
+ setNextAnimationTime(mNextAnimationTime + minDurationMillis);
+ Animator inAnimator = getInAnimator();
+ inAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
+ }
+ });
+ animator.playSequentially(getOutAnimator(), inAnimator);
+ } else {
+ Animator outAnimator = getOutAnimator();
+ outAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
+ }
+ });
+ animator.play(outAnimator);
+ }
- // Make sure each animation is visible for a minimum amount of time, while not worrying
- // about fading in blank text
- long timeInMillis = System.currentTimeMillis();
- long delay = Math.max(0, mNextAnimationTime - timeInMillis);
- setNextAnimationTime(timeInMillis + delay + getFadeOutDuration());
-
- final long minDurationMillis =
- (indication != null && indication.getMinVisibilityMillis() != null)
- ? indication.getMinVisibilityMillis()
- : MSG_MIN_DURATION_MILLIS_DEFAULT;
-
- if (!text.equals("") || hasIcon) {
- setNextAnimationTime(mNextAnimationTime + minDurationMillis);
- animSetBuilder.before(getInAnimator());
+ animator.setStartDelay(delay);
+ animator.start();
+ } else {
+ setAlpha(1f);
+ setTranslationY(0f);
+ setNextIndication();
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
}
-
- animSet.setStartDelay(delay);
- animSet.start();
}
private AnimatorSet getOutAnimator() {
@@ -143,29 +184,8 @@
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
- KeyguardIndication info = mKeyguardIndicationInfo.poll();
- if (info != null) {
- // First, update the style.
- // If a background is set on the text, we don't want shadow on the text
- if (info.getBackground() != null) {
- setTextAppearance(sButtonStyleId);
- } else {
- setTextAppearance(sStyleId);
- }
- setBackground(info.getBackground());
- setTextColor(info.getTextColor());
- setOnClickListener(info.getClickListener());
- setClickable(info.getClickListener() != null);
- final Drawable icon = info.getIcon();
- if (icon != null) {
- icon.setTint(getCurrentTextColor());
- if (icon instanceof AnimatedVectorDrawable) {
- ((AnimatedVectorDrawable) icon).start();
- }
- }
- setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
- }
- setText(mMessages.poll());
+ super.onAnimationEnd(animator);
+ setNextIndication();
}
});
@@ -177,6 +197,32 @@
return animatorSet;
}
+ private void setNextIndication() {
+ KeyguardIndication info = mKeyguardIndicationInfo.poll();
+ if (info != null) {
+ // First, update the style.
+ // If a background is set on the text, we don't want shadow on the text
+ if (info.getBackground() != null) {
+ setTextAppearance(sButtonStyleId);
+ } else {
+ setTextAppearance(sStyleId);
+ }
+ setBackground(info.getBackground());
+ setTextColor(info.getTextColor());
+ setOnClickListener(info.getClickListener());
+ setClickable(info.getClickListener() != null);
+ final Drawable icon = info.getIcon();
+ if (icon != null) {
+ icon.setTint(getCurrentTextColor());
+ if (icon instanceof AnimatedVectorDrawable) {
+ ((AnimatedVectorDrawable) icon).start();
+ }
+ }
+ setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
+ }
+ setText(mMessages.poll());
+ }
+
private AnimatorSet getInAnimator() {
AnimatorSet animatorSet = new AnimatorSet();
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(this, View.ALPHA, 1f);
@@ -190,6 +236,7 @@
yTranslate.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
setTranslationY(0);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 5feb405..9055081 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -100,13 +100,8 @@
}
@Override
- public void onOverlayChanged() {
- mView.onOverlayChanged();
- KeyguardStatusBarViewController.this.onThemeChanged();
- }
-
- @Override
public void onThemeChanged() {
+ mView.onOverlayChanged();
KeyguardStatusBarViewController.this.onThemeChanged();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 27770c7..8c8e4e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -443,7 +443,6 @@
private ArrayList<Consumer<ExpandableNotificationRow>>
mTrackingHeadsUpListeners =
new ArrayList<>();
- private Runnable mVerticalTranslationListener;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private int mPanelAlpha;
@@ -984,7 +983,7 @@
Utils.shouldUseSplitNotificationShade(mResources);
mScrimController.setClipsQsScrim(!mShouldUseSplitNotificationShade);
if (mQs != null) {
- mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade);
+ mQs.setInSplitShade(mShouldUseSplitNotificationShade);
}
int topMargin = mShouldUseSplitNotificationShade ? mSplitShadeStatusBarHeight :
@@ -2202,7 +2201,7 @@
private void updateQsExpansion() {
if (mQs == null) return;
float qsExpansionFraction = computeQsExpansionFraction();
- mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
+ mQs.setQsExpansion(qsExpansionFraction, getExpandedFraction(), getHeaderTranslation());
mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
@@ -3241,7 +3240,6 @@
@Override
protected void onClosingFinished() {
super.onClosingFinished();
- resetHorizontalPanelPosition();
setClosingWithAlphaFadeout(false);
mMediaHierarchyManager.closeGuts();
}
@@ -3251,47 +3249,6 @@
mNotificationStackScrollLayoutController.forceNoOverlappingRendering(closing);
}
- /**
- * Updates the horizontal position of the panel so it is positioned closer to the touch
- * responsible for opening the panel.
- *
- * @param x the x-coordinate the touch event
- */
- protected void updateHorizontalPanelPosition(float x) {
- if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth()
- || mShouldUseSplitNotificationShade) {
- resetHorizontalPanelPosition();
- return;
- }
- float leftMost = mPositionMinSideMargin
- + mNotificationStackScrollLayoutController.getWidth() / 2;
- float
- rightMost =
- mView.getWidth() - mPositionMinSideMargin
- - mNotificationStackScrollLayoutController.getWidth() / 2;
- if (Math.abs(x - mView.getWidth() / 2)
- < mNotificationStackScrollLayoutController.getWidth() / 4) {
- x = mView.getWidth() / 2;
- }
- x = Math.min(rightMost, Math.max(leftMost, x));
- float
- center = mNotificationStackScrollLayoutController.getLeft()
- + mNotificationStackScrollLayoutController.getWidth() / 2;
- setHorizontalPanelTranslation(x - center);
- }
-
- private void resetHorizontalPanelPosition() {
- setHorizontalPanelTranslation(0f);
- }
-
- protected void setHorizontalPanelTranslation(float translation) {
- mNotificationStackScrollLayoutController.setTranslationX(translation);
- mQsFrame.setTranslationX(translation);
- if (mVerticalTranslationListener != null) {
- mVerticalTranslationListener.run();
- }
- }
-
protected void updateExpandedHeight(float expandedHeight) {
if (mTracking) {
mNotificationStackScrollLayoutController
@@ -3338,9 +3295,9 @@
* cases, such as if there's a heads-up notification.
*/
public void setPanelScrimMinFraction(float minFraction) {
- mBar.onPanelMinFractionChanged(minFraction);
mMinFraction = minFraction;
mDepthController.setPanelPullDownMinFraction(mMinFraction);
+ mScrimController.setPanelScrimMinFraction(mMinFraction);
}
public void clearNotificationEffects() {
@@ -3456,7 +3413,7 @@
mQs.setExpandClickListener(mOnClickListener);
mQs.setHeaderClickable(isQsExpansionEnabled());
mQs.setOverscrolling(mStackScrollerOverscrolling);
- mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade);
+ mQs.setInSplitShade(mShouldUseSplitNotificationShade);
// recompute internal state when qspanel height changes
mQs.getView().addOnLayoutChangeListener(
@@ -3604,10 +3561,6 @@
mTrackingHeadsUpListeners.remove(listener);
}
- public void setVerticalTranslationListener(Runnable verticalTranslationListener) {
- mVerticalTranslationListener = verticalTranslationListener;
- }
-
public void setHeadsUpAppearanceController(
HeadsUpAppearanceController headsUpAppearanceController) {
mHeadsUpAppearanceController = headsUpAppearanceController;
@@ -3875,7 +3828,6 @@
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
- updateHorizontalPanelPosition(event.getX());
handled = true;
}
@@ -4259,12 +4211,7 @@
@Override
public void onThemeChanged() {
if (DEBUG) Log.d(TAG, "onThemeChanged");
- final int themeResId = mView.getContext().getThemeResId();
- if (mThemeResId == themeResId) {
- return;
- }
- mThemeResId = themeResId;
-
+ mThemeResId = mView.getContext().getThemeResId();
reInflateViews();
}
@@ -4278,12 +4225,6 @@
}
@Override
- public void onOverlayChanged() {
- if (DEBUG) Log.d(TAG, "onOverlayChanged");
- reInflateViews();
- }
-
- @Override
public void onDensityOrFontScaleChanged() {
if (DEBUG) Log.d(TAG, "onDensityOrFontScaleChanged");
reInflateViews();
@@ -4382,7 +4323,6 @@
// The update needs to happen after the headerSlide in above, otherwise the translation
// would reset
maybeAnimateBottomAreaAlpha();
- resetHorizontalPanelPosition();
updateQsState();
mSplitShadeHeaderController.setShadeExpanded(
mBarState == SHADE || mBarState == SHADE_LOCKED);
@@ -4608,9 +4548,6 @@
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mAffordanceHelper.onConfigurationChanged();
- if (newConfig.orientation != mLastOrientation) {
- resetHorizontalPanelPosition();
- }
mLastOrientation = newConfig.orientation;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index 1f1090d..310fe73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -146,11 +146,6 @@
}
/**
- * Percentage of panel expansion offset, caused by pulling down on a heads-up.
- */
- abstract void onPanelMinFractionChanged(float minFraction);
-
- /**
* @param frac the fraction from the expansion in [0, 1]
* @param expanded whether the panel is currently expanded; this is independent from the
* fraction as the panel also might be expanded if the fraction is 0
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 768567b..c23577c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -385,11 +385,16 @@
final boolean expand;
if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
- // If we get a cancel, put the shade back to the state it was in when the gesture
- // started
- if (onKeyguard) {
+ // If the keyguard is fading away, don't expand it again. This can happen if you're
+ // swiping to unlock, the app below the keyguard is in landscape, and the screen
+ // rotates while your finger is still down after the swipe to unlock.
+ if (mKeyguardStateController.isKeyguardFadingAway()) {
+ expand = false;
+ } else if (onKeyguard) {
expand = true;
} else {
+ // If we get a cancel, put the shade back to the state it was in when the
+ // gesture started
expand = !mPanelClosedOnDown;
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 1cca477..150d9c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -18,8 +18,6 @@
import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
-import static java.lang.Float.isNaN;
-
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
@@ -57,7 +55,6 @@
StatusBar mBar;
private ScrimController mScrimController;
- private float mMinFraction;
private Runnable mHideExpandedRunnable = new Runnable() {
@Override
public void run() {
@@ -268,20 +265,8 @@
}
@Override
- public void onPanelMinFractionChanged(float minFraction) {
- if (isNaN(minFraction)) {
- throw new IllegalArgumentException("minFraction cannot be NaN");
- }
- if (mMinFraction != minFraction) {
- mMinFraction = minFraction;
- updateScrimFraction();
- }
- }
-
- @Override
public void panelExpansionChanged(float frac, boolean expanded) {
super.panelExpansionChanged(frac, expanded);
- updateScrimFraction();
if ((frac == 0 || frac == 1)) {
if (mPanelExpansionStateChangedListener != null) {
mPanelExpansionStateChangedListener.onPanelExpansionStateChanged();
@@ -302,15 +287,6 @@
mPanelEnabledProvider = panelEnabledProvider;
}
- private void updateScrimFraction() {
- float scrimFraction = mPanelFraction;
- if (mMinFraction < 1.0f) {
- scrimFraction = Math.max((mPanelFraction - mMinFraction) / (1.0f - mMinFraction),
- 0);
- }
- mScrimController.setPanelExpansion(scrimFraction);
- }
-
public void updateResources() {
mCutoutSideNudge = getResources().getDimensionPixelSize(
R.dimen.display_cutout_margin_consumption);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index a564637..a5cea06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -34,6 +34,7 @@
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
+import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -183,8 +184,10 @@
private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
private final float mDefaultScrimAlpha;
- // Assuming the shade is expanded during initialization
- private float mPanelExpansion = 1f;
+ private float mRawPanelExpansionFraction;
+ private float mPanelScrimMinFraction;
+ // Calculated based on mRawPanelExpansionFraction and mPanelScrimMinFraction
+ private float mPanelExpansionFraction = 1f; // Assume shade is expanded during initialization
private float mQsExpansion;
private boolean mQsBottomVisible;
@@ -262,11 +265,6 @@
}
@Override
- public void onOverlayChanged() {
- ScrimController.this.onThemeChanged();
- }
-
- @Override
public void onUiModeChanged() {
ScrimController.this.onThemeChanged();
}
@@ -483,14 +481,39 @@
*
* The expansion fraction is tied to the scrim opacity.
*
- * @param fraction From 0 to 1 where 0 means collapsed and 1 expanded.
+ * See {@link PanelBar#panelExpansionChanged}.
+ *
+ * @param rawPanelExpansionFraction From 0 to 1 where 0 means collapsed and 1 expanded.
*/
- public void setPanelExpansion(float fraction) {
- if (isNaN(fraction)) {
- throw new IllegalArgumentException("Fraction should not be NaN");
+ public void setRawPanelExpansionFraction(
+ @FloatRange(from = 0.0, to = 1.0) float rawPanelExpansionFraction) {
+ if (isNaN(rawPanelExpansionFraction)) {
+ throw new IllegalArgumentException("rawPanelExpansionFraction should not be NaN");
}
- if (mPanelExpansion != fraction) {
- mPanelExpansion = fraction;
+ mRawPanelExpansionFraction = rawPanelExpansionFraction;
+ calculateAndUpdatePanelExpansion();
+ }
+
+ /** See {@link NotificationPanelViewController#setPanelScrimMinFraction(float)}. */
+ public void setPanelScrimMinFraction(float minFraction) {
+ if (isNaN(minFraction)) {
+ throw new IllegalArgumentException("minFraction should not be NaN");
+ }
+ mPanelScrimMinFraction = minFraction;
+ calculateAndUpdatePanelExpansion();
+ }
+
+ private void calculateAndUpdatePanelExpansion() {
+ float panelExpansionFraction = mRawPanelExpansionFraction;
+ if (mPanelScrimMinFraction < 1.0f) {
+ panelExpansionFraction = Math.max(
+ (mRawPanelExpansionFraction - mPanelScrimMinFraction)
+ / (1.0f - mPanelScrimMinFraction),
+ 0);
+ }
+
+ if (mPanelExpansionFraction != panelExpansionFraction) {
+ mPanelExpansionFraction = panelExpansionFraction;
boolean relevantState = (mState == ScrimState.UNLOCKED
|| mState == ScrimState.KEYGUARD
@@ -892,7 +915,8 @@
}
private float getInterpolatedFraction() {
- return Interpolators.getNotificationScrimAlpha(mPanelExpansion, false /* notification */);
+ return Interpolators.getNotificationScrimAlpha(
+ mPanelExpansionFraction, false /* notification */);
}
private void setScrimAlpha(ScrimView scrim, float alpha) {
@@ -1228,8 +1252,8 @@
pw.println(mTracking);
pw.print(" mDefaultScrimAlpha=");
pw.println(mDefaultScrimAlpha);
- pw.print(" mExpansionFraction=");
- pw.println(mPanelExpansion);
+ pw.print(" mPanelExpansionFraction=");
+ pw.println(mPanelExpansionFraction);
pw.print(" mExpansionAffectsAlpha=");
pw.println(mExpansionAffectsAlpha);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 7b3fc84..2bdb0edc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -336,6 +336,7 @@
}
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ private boolean mCallingFadingAwayAfterReveal;
private StatusBarCommandQueueCallbacks mCommandQueueCallbacks;
void setWindowState(int state) {
@@ -899,6 +900,8 @@
lockscreenShadeTransitionController.setStatusbar(this);
mExpansionChangedListeners = new ArrayList<>();
+ addExpansionChangedListener(
+ (expansion, expanded) -> mScrimController.setRawPanelExpansionFraction(expansion));
mBubbleExpandListener =
(isExpanding, key) -> mContext.getMainExecutor().execute(() -> {
@@ -1404,6 +1407,12 @@
mDeviceProvisionedController.addCallback(mUserSetupObserver);
mUserSetupObserver.onUserSetupChanged();
+ for (ExpansionChangedListener listener : mExpansionChangedListeners) {
+ // The initial expansion amount comes from mNotificationPanelViewController, so we
+ // should send the amount once we've fully set up that controller.
+ sendInitialExpansionAmount(listener);
+ }
+
// disable profiling bars, since they overlap and clutter the output on app windows
ThreadedRenderer.overrideProperty("disableProfileBars", "true");
@@ -3131,8 +3140,20 @@
public void fadeKeyguardWhilePulsing() {
mNotificationPanelViewController.fadeOut(0, FADE_KEYGUARD_DURATION_PULSING,
()-> {
- hideKeyguard();
- mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ Runnable finishFading = () -> {
+ mCallingFadingAwayAfterReveal = false;
+ hideKeyguard();
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ };
+ if (mLightRevealScrim.getRevealAmount() != 1.0f) {
+ mCallingFadingAwayAfterReveal = true;
+ // We're still revealing the Light reveal, let's only go to keyguard once
+ // that has finished and nothing moves anymore.
+ // Going there introduces lots of jank
+ mLightRevealScrim.setFullyRevealedRunnable(finishFading);
+ } else {
+ finishFading.run();
+ }
}).start();
}
@@ -3266,16 +3287,16 @@
* Switches theme from light to dark and vice-versa.
*/
protected void updateTheme() {
-
// Lock wallpaper defines the color of the majority of the views, hence we'll use it
// to set our default theme.
final boolean lockDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
final int themeResId = lockDarkText ? R.style.Theme_SystemUI_LightWallpaper
: R.style.Theme_SystemUI;
- if (mContext.getThemeResId() != themeResId) {
- mContext.setTheme(themeResId);
- mConfigurationController.notifyThemeChanged();
+ if (mContext.getThemeResId() == themeResId) {
+ return;
}
+ mContext.setTheme(themeResId);
+ mConfigurationController.notifyThemeChanged();
}
private void updateDozingState() {
@@ -4233,9 +4254,11 @@
}
private void sendInitialExpansionAmount(ExpansionChangedListener expansionChangedListener) {
- expansionChangedListener.onExpansionChanged(
- mNotificationPanelViewController.getExpandedFraction(),
- mNotificationPanelViewController.isExpanded());
+ if (mNotificationPanelViewController != null) {
+ expansionChangedListener.onExpansionChanged(
+ mNotificationPanelViewController.getExpandedFraction(),
+ mNotificationPanelViewController.isExpanded());
+ }
}
public void removeExpansionChangedListener(@NonNull ExpansionChangedListener listener) {
@@ -4291,7 +4314,7 @@
+ "mStatusBarKeyguardViewManager was null");
return;
}
- if (mKeyguardStateController.isKeyguardFadingAway()) {
+ if (mKeyguardStateController.isKeyguardFadingAway() && !mCallingFadingAwayAfterReveal) {
mStatusBarKeyguardViewManager.onKeyguardFadedAway();
}
}
@@ -4386,6 +4409,13 @@
@Override
public void onThemeChanged() {
+ if (mBrightnessMirrorController != null) {
+ mBrightnessMirrorController.onOverlayChanged();
+ }
+ // We need the new R.id.keyguard_indication_area before recreating
+ // mKeyguardIndicationController
+ mNotificationPanelViewController.onThemeChanged();
+
if (mStatusBarKeyguardViewManager != null) {
mStatusBarKeyguardViewManager.onThemeChanged();
}
@@ -4396,17 +4426,6 @@
}
@Override
- public void onOverlayChanged() {
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.onOverlayChanged();
- }
- // We need the new R.id.keyguard_indication_area before recreating
- // mKeyguardIndicationController
- mNotificationPanelViewController.onThemeChanged();
- onThemeChanged();
- }
-
- @Override
public void onUiModeChanged() {
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.onUiModeChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 61552f0..98be77d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -91,7 +91,7 @@
clearCachedInsets()
}
- override fun onOverlayChanged() {
+ override fun onThemeChanged() {
clearCachedInsets()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 832f317..c655964 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -25,7 +25,6 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
@@ -248,7 +247,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
onDensityOrFontScaleChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index d3d9063..eb405e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -83,7 +83,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
initResources();
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index b708861..235a8e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -208,9 +208,18 @@
apply(mCurrentState);
}
- /** Sets whether there is currently an ongoing call. */
- public void setIsCallOngoing(boolean isCallOngoing) {
- mCurrentState.mIsCallOngoing = isCallOngoing;
+ /**
+ * Sets whether an ongoing process requires the status bar to be forced visible.
+ *
+ * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing
+ * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to
+ * false but this method is set to true, then the status bar **will** be visible.
+ *
+ * TODO(b/195839150): We should likely merge this method and
+ * {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead.
+ */
+ public void setOngoingProcessRequiresStatusBarVisible(boolean visible) {
+ mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible;
apply(mCurrentState);
}
@@ -254,13 +263,14 @@
private static class State {
boolean mForceStatusBarVisible;
boolean mIsLaunchAnimationRunning;
- boolean mIsCallOngoing;
+ boolean mOngoingProcessRequiresStatusBarVisible;
}
private void applyForceStatusBarVisibleFlag(State state) {
if (state.mForceStatusBarVisible
|| state.mIsLaunchAnimationRunning
- || state.mIsCallOngoing) {
+ // Don't force-show the status bar if the user has already dismissed it.
+ || state.mOngoingProcessRequiresStatusBarVisible) {
mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
} else {
mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
index 0c5502b..26ba31c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
@@ -43,11 +43,6 @@
@VisibleForTesting
final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
- public void onOverlayChanged() {
- mView.updateColor();
- }
-
- @Override
public void onUiModeChanged() {
mView.updateColor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 143aaba..f3f3325 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -4,10 +4,10 @@
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
-import android.content.res.Configuration
import android.database.ContentObserver
import android.os.Handler
import android.provider.Settings
+import android.view.Surface
import android.view.View
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
@@ -239,10 +239,11 @@
return false
}
- // If we're not allowed to rotate the keyguard, then only do the screen off animation if
- // we're in portrait. Otherwise, AOD will animate in sideways, which looks weird.
+ // If we're not allowed to rotate the keyguard, it can only be displayed in zero-degree
+ // portrait. If we're in another orientation, disable the screen off animation so we don't
+ // animate in the keyguard AOD UI sideways or upside down.
if (!keyguardStateController.isKeyguardScreenRotationAllowed &&
- context.resources.configuration.orientation != Configuration.ORIENTATION_PORTRAIT) {
+ context.display.rotation != Surface.ROTATION_0) {
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index b7c80de..3806d9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -34,6 +34,8 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
@@ -60,8 +62,11 @@
private val logger: OngoingCallLogger,
private val dumpManager: DumpManager,
private val statusBarWindowController: Optional<StatusBarWindowController>,
+ private val swipeStatusBarAwayGestureHandler: Optional<SwipeStatusBarAwayGestureHandler>,
+ private val statusBarStateController: StatusBarStateController,
) : CallbackController<OngoingCallListener>, Dumpable {
+ private var isFullscreen: Boolean = false
/** Non-null if there's an active call notification. */
private var callNotificationInfo: CallNotificationInfo? = null
/** True if the application managing the call is visible to the user. */
@@ -96,7 +101,8 @@
entry.sbn.notification.contentIntent?.intent,
entry.sbn.uid,
entry.sbn.notification.extras.getInt(
- Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING
+ Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING,
+ statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
)
if (newOngoingCallInfo == callNotificationInfo) {
return
@@ -122,6 +128,7 @@
dumpManager.registerDumpable(this)
if (featureFlags.isOngoingCallStatusBarChipEnabled) {
notifCollection.addCollectionListener(notifListener)
+ statusBarStateController.addCallback(statusBarStateListener)
}
}
@@ -175,10 +182,8 @@
val currentChipView = chipView
val timeView = currentChipView?.getTimeView()
- val backgroundView =
- currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
- if (currentChipView != null && timeView != null && backgroundView != null) {
+ if (currentChipView != null && timeView != null) {
if (currentCallNotificationInfo.hasValidStartTime()) {
timeView.setShouldHideText(false)
timeView.base = currentCallNotificationInfo.callStartTime -
@@ -189,22 +194,15 @@
timeView.setShouldHideText(true)
timeView.stop()
}
-
- currentCallNotificationInfo.intent?.let { intent ->
- currentChipView.setOnClickListener {
- logger.logChipClicked()
- activityStarter.postStartActivityDismissingKeyguard(
- intent,
- 0,
- ActivityLaunchAnimator.Controller.fromView(
- backgroundView,
- InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
- )
- }
- }
+ updateChipClickListener()
setUpUidObserver(currentCallNotificationInfo)
- statusBarWindowController.ifPresent { it.setIsCallOngoing(true) }
+ if (!currentCallNotificationInfo.statusBarSwipedAway) {
+ statusBarWindowController.ifPresent {
+ it.setOngoingProcessRequiresStatusBarVisible(true)
+ }
+ }
+ updateGestureListening()
mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
} else {
// If we failed to update the chip, don't store the call info. Then [hasOngoingCall]
@@ -218,6 +216,30 @@
}
}
+ private fun updateChipClickListener() {
+ if (callNotificationInfo == null) { return }
+ if (isFullscreen && !featureFlags.isOngoingCallInImmersiveChipTapEnabled) {
+ chipView?.setOnClickListener(null)
+ } else {
+ val currentChipView = chipView
+ val backgroundView =
+ currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
+ val intent = callNotificationInfo?.intent
+ if (currentChipView != null && backgroundView != null && intent != null) {
+ currentChipView.setOnClickListener {
+ logger.logChipClicked()
+ activityStarter.postStartActivityDismissingKeyguard(
+ intent,
+ 0,
+ ActivityLaunchAnimator.Controller.fromView(
+ backgroundView,
+ InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
+ )
+ }
+ }
+ }
+ }
+
/**
* Sets up an [IUidObserver] to monitor the status of the application managing the ongoing call.
*/
@@ -268,10 +290,23 @@
return procState <= ActivityManager.PROCESS_STATE_TOP
}
+ private fun updateGestureListening() {
+ if (callNotificationInfo == null
+ || callNotificationInfo?.statusBarSwipedAway == true
+ || !isFullscreen) {
+ swipeStatusBarAwayGestureHandler.ifPresent { it.removeOnGestureDetectedCallback(TAG) }
+ } else {
+ swipeStatusBarAwayGestureHandler.ifPresent {
+ it.addOnGestureDetectedCallback(TAG, this::onSwipeAwayGestureDetected)
+ }
+ }
+ }
+
private fun removeChip() {
callNotificationInfo = null
tearDownChipView()
- statusBarWindowController.ifPresent { it.setIsCallOngoing(false) }
+ statusBarWindowController.ifPresent { it.setOngoingProcessRequiresStatusBarVisible(false) }
+ swipeStatusBarAwayGestureHandler.ifPresent { it.removeOnGestureDetectedCallback(TAG) }
mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
if (uidObserver != null) {
iActivityManager.unregisterUidObserver(uidObserver)
@@ -286,13 +321,42 @@
return this.findViewById(R.id.ongoing_call_chip_time)
}
+ /**
+ * If there's an active ongoing call, then we will force the status bar to always show, even if
+ * the user is in immersive mode. However, we also want to give users the ability to swipe away
+ * the status bar if they need to access the area under the status bar.
+ *
+ * This method updates the status bar window appropriately when the swipe away gesture is
+ * detected.
+ */
+ private fun onSwipeAwayGestureDetected() {
+ if (DEBUG) { Log.d(TAG, "Swipe away gesture detected") }
+ callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
+ statusBarWindowController.ifPresent {
+ it.setOngoingProcessRequiresStatusBarVisible(false)
+ }
+ swipeStatusBarAwayGestureHandler.ifPresent {
+ it.removeOnGestureDetectedCallback(TAG)
+ }
+ }
+
+ private val statusBarStateListener = object : StatusBarStateController.StateListener {
+ override fun onFullscreenStateChanged(isFullscreen: Boolean) {
+ this@OngoingCallController.isFullscreen = isFullscreen
+ updateChipClickListener()
+ updateGestureListening()
+ }
+ }
+
private data class CallNotificationInfo(
val key: String,
val callStartTime: Long,
val intent: Intent?,
val uid: Int,
/** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
- val isOngoing: Boolean
+ val isOngoing: Boolean,
+ /** True if the user has swiped away the status bar while in this phone call. */
+ val statusBarSwipedAway: Boolean
) {
/**
* Returns true if the notification information has a valid call start time.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index e679c4c..6b80a9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -38,7 +38,6 @@
default void onDensityOrFontScaleChanged() {}
default void onSmallestScreenWidthChanged() {}
default void onMaxBoundsChanged() {}
- default void onOverlayChanged() {}
default void onUiModeChanged() {}
default void onThemeChanged() {}
default void onLocaleListChanged() {}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index db965db..c776ab9 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -212,7 +212,7 @@
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
pip.onOverlayChanged();
}
});
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 5c7885f..fe0a2a4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -30,6 +29,7 @@
import android.testing.AndroidTestingRunner;
import android.view.View;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import androidx.test.filters.SmallTest;
@@ -108,7 +108,7 @@
private final View mFakeSmartspaceView = new View(mContext);
private KeyguardClockSwitchController mController;
- private View mStatusArea;
+ private View mSliceView;
@Before
public void setup() {
@@ -149,8 +149,10 @@
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
- mStatusArea = new View(getContext());
- when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
+ mSliceView = new View(getContext());
+ when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
+ when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(
+ new LinearLayout(getContext()));
}
@Test
@@ -215,7 +217,7 @@
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mController.init();
- assertEquals(View.GONE, mStatusArea.getVisibility());
+ assertEquals(View.GONE, mSliceView.getVisibility());
}
@Test
@@ -223,22 +225,7 @@
when(mSmartspaceController.isEnabled()).thenReturn(false);
mController.init();
- assertEquals(View.VISIBLE, mStatusArea.getVisibility());
- }
-
- @Test
- public void testDetachDisconnectsSmartspace() {
- when(mSmartspaceController.isEnabled()).thenReturn(true);
- when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
- mController.init();
- verify(mView).addView(eq(mFakeSmartspaceView), anyInt(), any());
-
- ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
- verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
-
- listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
- verify(mSmartspaceController).disconnect();
+ assertEquals(View.VISIBLE, mSliceView.getVisibility());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 1ab08c2..77302ce 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -54,7 +54,7 @@
MockitoAnnotations.initMocks(this);
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
mKeyguardSliceView = (KeyguardSliceView) layoutInflater
- .inflate(R.layout.keyguard_status_area, null);
+ .inflate(R.layout.keyguard_slice_view, null);
mSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
SliceProvider.setSpecs(new HashSet<>(Collections.singletonList(SliceSpecs.LIST)));
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 0772b20..3edfd03 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -64,7 +64,6 @@
import android.os.IRemoteCallback;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.Vibrator;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -173,8 +172,6 @@
private FeatureFlags mFeatureFlags;
@Mock
private InteractionJankMonitor mInteractionJankMonitor;
- @Mock
- private Vibrator mVibrator;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
// Direct executor
@@ -242,8 +239,6 @@
when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
- when(mFeatureFlags.isKeyguardLayoutEnabled()).thenReturn(false);
-
mMockitoSession = ExtendedMockito.mockitoSession()
.spyStatic(SubscriptionManager.class).startMocking();
ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
@@ -1064,7 +1059,7 @@
mRingerModeTracker, mBackgroundExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager, mFeatureFlags,
- mInteractionJankMonitor, mVibrator);
+ mInteractionJankMonitor);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
new file mode 100644
index 0000000..172dcda
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.systemui.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class FeatureFlagManagerTest extends SysuiTestCase {
+ FeatureFlagManager mFeatureFlagManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mFeatureFlagManager = new FeatureFlagManager();
+ }
+
+ @Test
+ public void testIsEnabled() {
+ mFeatureFlagManager.setEnabled(1, true);
+ // Again, nothing changes.
+ assertThat(mFeatureFlagManager.isEnabled(1, false)).isFalse();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
index 7bc5f86..fc6f3fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
@@ -32,6 +32,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.util.wrapper.BuildInfo;
@@ -44,8 +45,9 @@
public class FeatureFlagReaderTest extends SysuiTestCase {
@Mock private Resources mResources;
@Mock private BuildInfo mBuildInfo;
- @Mock private PluginManager mPluginManager;
+ @Mock private DumpManager mDumpManager;
@Mock private SystemPropertiesHelper mSystemPropertiesHelper;
+ @Mock private FlagReader mFlagReader;
private FeatureFlagReader mReader;
@@ -66,7 +68,7 @@
when(mBuildInfo.isDebuggable()).thenReturn(isDebuggable);
when(mResources.getBoolean(R.bool.are_flags_overrideable)).thenReturn(isOverrideable);
mReader = new FeatureFlagReader(
- mResources, mBuildInfo, mPluginManager, mSystemPropertiesHelper);
+ mResources, mBuildInfo, mDumpManager, mSystemPropertiesHelper, mFlagReader);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
index 1a96178..a850f70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
@@ -23,7 +23,6 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.FlagReaderPlugin;
import org.junit.Before;
import org.junit.Test;
@@ -51,11 +50,10 @@
mFeatureFlags.addFlag(flag);
// Assert and capture that a plugin listener was added.
- ArgumentCaptor<FlagReaderPlugin.Listener> pluginListenerCaptor =
- ArgumentCaptor.forClass(FlagReaderPlugin.Listener.class);
-
+ ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor =
+ ArgumentCaptor.forClass(FlagReader.Listener.class);
verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
- FlagReaderPlugin.Listener pluginListener = pluginListenerCaptor.getValue();
+ FlagReader.Listener pluginListener = pluginListenerCaptor.getValue();
// Signal a change. No listeners, so no real effect.
pluginListener.onFlagChanged(flag.getId());
@@ -81,11 +79,10 @@
mFeatureFlags.addFlag(flag);
// Assert and capture that a plugin listener was added.
- ArgumentCaptor<FlagReaderPlugin.Listener> pluginListenerCaptor =
- ArgumentCaptor.forClass(FlagReaderPlugin.Listener.class);
-
+ ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor =
+ ArgumentCaptor.forClass(FlagReader.Listener.class);
verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
- FlagReaderPlugin.Listener pluginListener = pluginListenerCaptor.getValue();
+ FlagReader.Listener pluginListener = pluginListenerCaptor.getValue();
// Add a listener for the flag
final Flag<?>[] changedFlag = {null};
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index c464cad..90e3db7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -18,14 +18,19 @@
import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Vibrator;
@@ -33,21 +38,25 @@
import android.testing.TestableLooper;
import android.util.DisplayMetrics;
import android.util.Pair;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.LockIconView;
import com.android.keyguard.LockIconViewController;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -69,7 +78,10 @@
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class LockIconViewControllerTest extends SysuiTestCase {
+ private static final String UNLOCKED_LABEL = "unlocked";
+
private @Mock LockIconView mLockIconView;
+ private @Mock AnimatedVectorDrawable mIconDrawable;
private @Mock Context mContext;
private @Mock Resources mResources;
private @Mock DisplayMetrics mDisplayMetrics;
@@ -86,6 +98,7 @@
private @Mock Vibrator mVibrator;
private @Mock AuthRippleController mAuthRippleController;
private @Mock LottieAnimationView mAodFp;
+ private @Mock LayoutInflater mLayoutInflater;
private LockIconViewController mLockIconViewController;
@@ -97,6 +110,11 @@
@Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
private AuthController.Callback mAuthControllerCallback;
+ @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback>
+ mKeyguardUpdateMonitorCallbackCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
+
@Captor private ArgumentCaptor<PointF> mPointCaptor;
@Before
@@ -105,9 +123,16 @@
when(mLockIconView.getResources()).thenReturn(mResources);
when(mLockIconView.getContext()).thenReturn(mContext);
+ when(mLockIconView.findViewById(R.layout.udfps_aod_lock_icon)).thenReturn(mAodFp);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
- when(mLockIconView.findViewById(anyInt())).thenReturn(mAodFp);
+ when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL);
+ when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable);
+
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
mLockIconViewController = new LockIconViewController(
mLockIconView,
@@ -122,11 +147,42 @@
mConfigurationController,
mDelayableExecutor,
mVibrator,
- mAuthRippleController
+ mAuthRippleController,
+ mResources,
+ mLayoutInflater
);
}
@Test
+ public void testIgnoreUdfpsWhenNotSupported() {
+ // GIVEN Udpfs sensor is NOT available
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN lottie animation should NOT be inflated
+ verify(mLayoutInflater, never()).inflate(eq(R.layout.udfps_aod_lock_icon), any());
+ }
+
+ @Test
+ public void testInflateUdfpsWhenSupported() {
+ // GIVEN Udpfs sensor is available
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN lottie animation should be inflated
+ verify(mLayoutInflater).inflate(eq(R.layout.udfps_aod_lock_icon), any());
+ }
+
+ @Test
public void testUpdateFingerprintLocationOnInit() {
// GIVEN fp sensor location is available pre-attached
Pair<Integer, PointF> udfps = setupUdfps();
@@ -192,6 +248,29 @@
verify(mLockIconView).setUseBackground(false);
}
+ @Test
+ public void testUnlockIconShows_biometricUnlockedTrue() {
+ // GIVEN UDFPS sensor location is available
+ setupUdfps();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+ captureKeyguardUpdateMonitorCallback();
+
+ // GIVEN user has unlocked with a biometric auth (ie: face auth)
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+ reset(mLockIconView);
+
+ // WHEN face auth's biometric running state changes
+ mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+ BiometricSourceType.FACE);
+
+ // THEN the unlock icon is shown
+ verify(mLockIconView).setContentDescription(UNLOCKED_LABEL);
+ }
+
private Pair<Integer, PointF> setupUdfps() {
final PointF udfpsLocation = new PointF(50, 75);
final int radius = 33;
@@ -220,4 +299,10 @@
verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture());
mAttachListener = mAttachCaptor.getValue();
}
+
+ private void captureKeyguardUpdateMonitorCallback() {
+ verify(mKeyguardUpdateMonitor).registerCallback(
+ mKeyguardUpdateMonitorCallbackCaptor.capture());
+ mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 42629f5..bf5a6e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -41,6 +41,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.FakeExecutor
@@ -95,6 +96,7 @@
@Mock private lateinit var collapsedSet: ConstraintSet
@Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
@Mock private lateinit var mediaCarouselController: MediaCarouselController
+ @Mock private lateinit var falsingManager: FalsingManager
private lateinit var appIcon: ImageView
private lateinit var albumView: ImageView
private lateinit var titleText: TextView
@@ -131,8 +133,8 @@
whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet)
player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
- seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil,
- mediaOutputDialogFactory, mediaCarouselController)
+ seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil,
+ mediaOutputDialogFactory, mediaCarouselController, falsingManager)
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
// Mock out a view holder for the player to attach to.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
index 7d8728e..e77802f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
@@ -76,6 +76,7 @@
assertThat(seekBarView.getThumb().getAlpha()).isEqualTo(0)
assertThat(elapsedTimeView.getText()).isEqualTo("")
assertThat(totalTimeView.getText()).isEqualTo("")
+ assertThat(seekBarView.contentDescription).isEqualTo("")
assertThat(seekBarView.maxHeight).isEqualTo(disabledHeight)
}
@@ -102,6 +103,9 @@
assertThat(seekBarView.max).isEqualTo(120000)
assertThat(elapsedTimeView.getText()).isEqualTo("00:03")
assertThat(totalTimeView.getText()).isEqualTo("02:00")
+
+ val desc = context.getString(R.string.controls_media_seekbar_description, "00:03", "02:00")
+ assertThat(seekBarView.contentDescription).isEqualTo(desc)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 25ca8c9..750600ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -119,7 +119,6 @@
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
@@ -181,7 +180,6 @@
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(mMediaDevices);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.VISIBLE);
}
@@ -190,7 +188,6 @@
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(new ArrayList<>());
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
}
@@ -198,7 +195,6 @@
public void onBindViewHolder_bindNonActiveConnectedDevice_verifyView() {
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
@@ -215,7 +211,6 @@
when(mMediaDevice2.isConnected()).thenReturn(false);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
@@ -231,7 +226,6 @@
LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java
index 1f85112..ca5d570 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java
@@ -99,7 +99,6 @@
assertThat(mGroupViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mGroupViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
@@ -114,7 +113,6 @@
assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
@@ -141,7 +139,6 @@
assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
@@ -167,7 +164,6 @@
assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
@@ -186,7 +182,6 @@
assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index c1a9739..2a8840b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -57,6 +57,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
@@ -118,7 +119,8 @@
mock(NavigationBarA11yHelper.class),
mock(TaskbarDelegate.class),
mock(UserTracker.class),
- mock(DumpManager.class)));
+ mock(DumpManager.class),
+ mock(AutoHideController.class)));
initializeNavigationBars();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index a1760a7..7e900c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -22,6 +22,7 @@
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PseudoGridView
@@ -68,6 +69,8 @@
private lateinit var launchView: View
@Mock
private lateinit var gridView: PseudoGridView
+ @Mock
+ private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
@Captor
private lateinit var clickCaptor: ArgumentCaptor<View.OnClickListener>
@@ -87,6 +90,7 @@
{ userDetailViewAdapter },
activityStarter,
falsingManager,
+ dialogLaunchAnimator,
{ dialog }
)
}
@@ -94,7 +98,7 @@
@Test
fun showDialog_callsDialogShow() {
controller.showDialog(launchView)
- verify(dialog).show()
+ verify(dialogLaunchAnimator).showFromView(dialog, launchView)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index f5cab1d..01f7fae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -166,7 +166,7 @@
private BroadcastReceiver mBroadcastReceiver;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
- private KeyguardIndicationTextView mTextView;
+ private KeyguardIndicationTextView mTextView; // AOD text
private KeyguardIndicationController mController;
private WakeLockFake.Builder mWakeLockBuilder;
@@ -412,41 +412,32 @@
@Test
public void transientIndication_holdsWakeLock_whenDozing() {
+ // GIVEN animations are enabled and text is visible
+ mTextView.setAnimationsEnabled(true);
createController();
+ mController.setVisible(true);
+ // WHEN transient text is shown
mStatusBarStateListener.onDozingChanged(true);
mController.showTransientIndication("Test");
- assertTrue(mWakeLock.isHeld());
+ // THEN wake lock is held while the animation is running
+ assertTrue("WakeLock expected: HELD, was: RELEASED", mWakeLock.isHeld());
}
@Test
- public void transientIndication_releasesWakeLock_afterHiding() {
+ public void transientIndication_releasesWakeLock_whenDozing() {
+ // GIVEN animations aren't enabled
+ mTextView.setAnimationsEnabled(false);
createController();
+ mController.setVisible(true);
+ // WHEN we show the transient indication
mStatusBarStateListener.onDozingChanged(true);
mController.showTransientIndication("Test");
- mController.hideTransientIndication();
- assertFalse(mWakeLock.isHeld());
- }
-
- @Test
- public void transientIndication_releasesWakeLock_afterHidingDelayed() throws Throwable {
- mInstrumentation.runOnMainSync(() -> {
- createController();
-
- mStatusBarStateListener.onDozingChanged(true);
- mController.showTransientIndication("Test");
- mController.hideTransientIndicationDelayed(0);
- });
- mInstrumentation.waitForIdleSync();
-
- Boolean[] held = new Boolean[1];
- mInstrumentation.runOnMainSync(() -> {
- held[0] = mWakeLock.isHeld();
- });
- assertFalse("WakeLock expected: RELEASED, was: HELD", held[0]);
+ // THEN wake lock is RELEASED, not held
+ assertFalse("WakeLock expected: RELEASED, was: HELD", mWakeLock.isHeld());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index c50296b..7938511 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -209,7 +209,7 @@
verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat())
verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(),
anyBoolean(), anyLong())
- verify(qS, never()).setTransitionToFullShadeAmount(anyFloat(), anyBoolean())
+ verify(qS, never()).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
}
@Test
@@ -220,7 +220,7 @@
verify(scrimController).setTransitionToFullShadeProgress(anyFloat())
verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(),
anyBoolean(), anyLong())
- verify(qS).setTransitionToFullShadeAmount(anyFloat(), anyBoolean())
+ verify(qS).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
verify(depthController).transitionToFullShadeProgress = anyFloat()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index ee9c2b82..ff91978 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -246,6 +246,7 @@
clearInvocations(plugin)
// WHEN the session is closed
+ controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View)
controller.disconnect()
// THEN the listener receives an empty list of targets
@@ -417,6 +418,7 @@
connectSession()
// WHEN we are told to cleanup
+ controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View)
controller.disconnect()
// THEN we disconnect from the session and unregister any listeners
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index bca1227..bafbccd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -198,7 +198,6 @@
mHeadsUpAppearanceController.destroy();
verify(mHeadsUpManager).removeListener(any());
verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any());
- verify(mPanelView).setVerticalTranslationListener(isNull());
verify(mPanelView).removeTrackingHeadsUpListener(any());
verify(mPanelView).setHeadsUpAppearanceController(isNull());
verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index b18ea4b..265e418 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -658,18 +658,6 @@
}
@Test
- public void testOnDragDownEvent_horizontalTranslationIsZeroForSplitShade() {
- when(mNotificationStackScrollLayoutController.getWidth()).thenReturn(350f);
- when(mView.getWidth()).thenReturn(800);
- enableSplitShade(/* enabled= */ true);
-
- onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN,
- 200f /* x position */, 0f, 0));
-
- verify(mQsFrame).setTranslationX(0);
- }
-
- @Test
public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 5ebe900..705112a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -623,7 +623,7 @@
@Test
public void transitionToUnlocked() {
- mScrimController.setPanelExpansion(0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -638,7 +638,7 @@
));
// Back scrim should be visible after start dragging
- mScrimController.setPanelExpansion(0.3f);
+ mScrimController.setRawPanelExpansionFraction(0.3f);
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mNotificationsScrim, SEMI_TRANSPARENT,
@@ -663,20 +663,20 @@
@Test
public void panelExpansion() {
- mScrimController.setPanelExpansion(0f);
- mScrimController.setPanelExpansion(0.5f);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.setRawPanelExpansionFraction(0.5f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
reset(mScrimBehind);
- mScrimController.setPanelExpansion(0f);
- mScrimController.setPanelExpansion(1.0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
assertEquals("Scrim alpha should change after setPanelExpansion",
mScrimBehindAlpha, mScrimBehind.getViewAlpha(), 0.01f);
- mScrimController.setPanelExpansion(0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
assertEquals("Scrim alpha should change after setPanelExpansion",
@@ -723,21 +723,21 @@
@Test
public void panelExpansionAffectsAlpha() {
- mScrimController.setPanelExpansion(0f);
- mScrimController.setPanelExpansion(0.5f);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.setRawPanelExpansionFraction(0.5f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
final float scrimAlpha = mScrimBehind.getViewAlpha();
reset(mScrimBehind);
mScrimController.setExpansionAffectsAlpha(false);
- mScrimController.setPanelExpansion(0.8f);
+ mScrimController.setRawPanelExpansionFraction(0.8f);
verifyZeroInteractions(mScrimBehind);
assertEquals("Scrim opacity shouldn't change when setExpansionAffectsAlpha "
+ "is false", scrimAlpha, mScrimBehind.getViewAlpha(), 0.01f);
mScrimController.setExpansionAffectsAlpha(true);
- mScrimController.setPanelExpansion(0.1f);
+ mScrimController.setRawPanelExpansionFraction(0.1f);
finishAnimationsImmediately();
Assert.assertNotEquals("Scrim opacity should change when setExpansionAffectsAlpha "
+ "is true", scrimAlpha, mScrimBehind.getViewAlpha(), 0.01f);
@@ -747,7 +747,7 @@
public void transitionToUnlockedFromOff() {
// Simulate unlock with fingerprint without AOD
mScrimController.transitionTo(ScrimState.OFF);
- mScrimController.setPanelExpansion(0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -769,7 +769,7 @@
public void transitionToUnlockedFromAod() {
// Simulate unlock with fingerprint
mScrimController.transitionTo(ScrimState.AOD);
- mScrimController.setPanelExpansion(0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -948,7 +948,7 @@
@Test
public void testConservesExpansionOpacityAfterTransition() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.setPanelExpansion(0.5f);
+ mScrimController.setRawPanelExpansionFraction(0.5f);
finishAnimationsImmediately();
final float expandedAlpha = mScrimBehind.getViewAlpha();
@@ -1075,7 +1075,7 @@
@Test
public void testScrimsOpaque_whenShadeFullyExpanded() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.setPanelExpansion(1);
+ mScrimController.setRawPanelExpansionFraction(1);
// notifications scrim alpha change require calling setQsPosition
mScrimController.setQsPosition(0, 300);
finishAnimationsImmediately();
@@ -1089,7 +1089,7 @@
@Test
public void testScrimsVisible_whenShadeVisible() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.setPanelExpansion(0.3f);
+ mScrimController.setRawPanelExpansionFraction(0.3f);
// notifications scrim alpha change require calling setQsPosition
mScrimController.setQsPosition(0, 300);
finishAnimationsImmediately();
@@ -1124,7 +1124,7 @@
public void testScrimsVisible_whenShadeVisible_clippingQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.setPanelExpansion(0.3f);
+ mScrimController.setRawPanelExpansionFraction(0.3f);
// notifications scrim alpha change require calling setQsPosition
mScrimController.setQsPosition(0.5f, 300);
finishAnimationsImmediately();
@@ -1150,7 +1150,7 @@
public void testNotificationScrimTransparent_whenOnLockscreen() {
mScrimController.transitionTo(ScrimState.KEYGUARD);
// even if shade is not pulled down, panel has expansion of 1 on the lockscreen
- mScrimController.setPanelExpansion(1);
+ mScrimController.setRawPanelExpansionFraction(1);
mScrimController.setQsPosition(0f, /*qs panel bottom*/ 0);
assertScrimAlpha(Map.of(
@@ -1160,7 +1160,7 @@
@Test
public void testNotificationScrimVisible_afterOpeningShadeFromLockscreen() {
- mScrimController.setPanelExpansion(1);
+ mScrimController.setRawPanelExpansionFraction(1);
mScrimController.transitionTo(ScrimState.SHADE_LOCKED);
finishAnimationsImmediately();
@@ -1203,11 +1203,11 @@
@Test
public void testNotificationTransparency_followsTransitionToFullShade() {
mScrimController.transitionTo(ScrimState.SHADE_LOCKED);
- mScrimController.setPanelExpansion(1.0f);
+ mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
float shadeLockedAlpha = mNotificationsScrim.getViewAlpha();
mScrimController.transitionTo(ScrimState.KEYGUARD);
- mScrimController.setPanelExpansion(1.0f);
+ mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
float keyguardAlpha = mNotificationsScrim.getViewAlpha();
@@ -1227,7 +1227,7 @@
}
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
- mScrimController.setPanelExpansion(expansion);
+ mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();
// alpha is not changing linearly thus 0.2 of leeway when asserting
assertEquals(expectedAlpha, mNotificationsScrim.getViewAlpha(), 0.2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index e97aba2..3d2ff47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -36,6 +36,8 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -50,8 +52,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.nullable
+import org.mockito.ArgumentMatchers.*
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.eq
@@ -83,10 +84,13 @@
private lateinit var controller: OngoingCallController
private lateinit var notifCollectionListener: NotifCollectionListener
+ @Mock private lateinit var mockFeatureFlags: FeatureFlags
+ @Mock private lateinit var mockSwipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler
@Mock private lateinit var mockOngoingCallListener: OngoingCallListener
@Mock private lateinit var mockActivityStarter: ActivityStarter
@Mock private lateinit var mockIActivityManager: IActivityManager
@Mock private lateinit var mockStatusBarWindowController: StatusBarWindowController
+ @Mock private lateinit var mockStatusBarStateController: StatusBarStateController
private lateinit var chipView: View
@@ -98,13 +102,12 @@
}
MockitoAnnotations.initMocks(this)
- val featureFlags = mock(FeatureFlags::class.java)
- `when`(featureFlags.isOngoingCallStatusBarChipEnabled).thenReturn(true)
+ `when`(mockFeatureFlags.isOngoingCallStatusBarChipEnabled).thenReturn(true)
val notificationCollection = mock(CommonNotifCollection::class.java)
controller = OngoingCallController(
notificationCollection,
- featureFlags,
+ mockFeatureFlags,
clock,
mockActivityStarter,
mainExecutor,
@@ -112,7 +115,9 @@
OngoingCallLogger(uiEventLoggerFake),
DumpManager(),
Optional.of(mockStatusBarWindowController),
- )
+ Optional.of(mockSwipeStatusBarAwayGestureHandler),
+ mockStatusBarStateController,
+ )
controller.init()
controller.addCallback(mockOngoingCallListener)
controller.setChipView(chipView)
@@ -141,7 +146,7 @@
fun onEntryUpdated_isOngoingCallNotif_windowControllerUpdated() {
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- verify(mockStatusBarWindowController).setIsCallOngoing(true)
+ verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(true)
}
@Test
@@ -242,7 +247,7 @@
notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
- verify(mockStatusBarWindowController).setIsCallOngoing(false)
+ verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(false)
}
/** Regression test for b/188491504. */
@@ -435,6 +440,120 @@
// Other tests for notifyChipVisibilityChanged are in [OngoingCallLogger], since
// [OngoingCallController.notifyChipVisibilityChanged] just delegates to that class.
+ @Test
+ fun callNotificationAdded_chipIsClickable() {
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun fullscreenIsTrue_thenCallNotificationAdded_chipNotClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ assertThat(chipView.hasOnClickListeners()).isFalse()
+ }
+
+ @Test
+ fun callNotificationAdded_thenFullscreenIsTrue_chipNotClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+
+ assertThat(chipView.hasOnClickListeners()).isFalse()
+ }
+
+ @Test
+ fun fullscreenChangesToFalse_chipClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ // First, update to true
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+ // Then, update to false
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ false)
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun fullscreenIsTrue_butChipClickInImmersiveEnabled_chipClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(true)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ // Swipe gesture tests
+
+ @Test
+ fun callStartedInImmersiveMode_swipeGestureCallbackAdded() {
+ getStateListener().onFullscreenStateChanged(true)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .addOnGestureDetectedCallback(anyString(), any())
+ }
+
+ @Test
+ fun callStartedNotInImmersiveMode_swipeGestureCallbackNotAdded() {
+ getStateListener().onFullscreenStateChanged(false)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ verify(mockSwipeStatusBarAwayGestureHandler, never())
+ .addOnGestureDetectedCallback(anyString(), any())
+ }
+
+ @Test
+ fun transitionToImmersiveMode_swipeGestureCallbackAdded() {
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ getStateListener().onFullscreenStateChanged(true)
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .addOnGestureDetectedCallback(anyString(), any())
+ }
+
+ @Test
+ fun transitionOutOfImmersiveMode_swipeGestureCallbackRemoved() {
+ getStateListener().onFullscreenStateChanged(true)
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ reset(mockSwipeStatusBarAwayGestureHandler)
+
+ getStateListener().onFullscreenStateChanged(false)
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .removeOnGestureDetectedCallback(anyString())
+ }
+
+ @Test
+ fun callEndedWhileInImmersiveMode_swipeGestureCallbackRemoved() {
+ getStateListener().onFullscreenStateChanged(true)
+ val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+ notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+ reset(mockSwipeStatusBarAwayGestureHandler)
+
+ notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .removeOnGestureDetectedCallback(anyString())
+ }
+
+ // TODO(b/195839150): Add test
+ // swipeGesturedTriggeredPreviously_entersImmersiveModeAgain_callbackNotAdded(). That's
+ // difficult to add now because we have no way to trigger [SwipeStatusBarAwayGestureHandler]'s
+ // callbacks in test.
+
+ // END swipe gesture tests
+
private fun createOngoingCallNotifEntry() = createCallNotifEntry(ongoingCallStyle)
private fun createScreeningCallNotifEntry() = createCallNotifEntry(screeningCallStyle)
@@ -459,6 +578,13 @@
}
private fun createNotCallNotifEntry() = NotificationEntryBuilder().build()
+
+ private fun getStateListener(): StatusBarStateController.StateListener {
+ val statusBarStateListenerCaptor = ArgumentCaptor.forClass(
+ StatusBarStateController.StateListener::class.java)
+ verify(mockStatusBarStateController).addCallback(statusBarStateListenerCaptor.capture())
+ return statusBarStateListenerCaptor.value!!
+ }
}
private val person = Person.Builder().setName("name").build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
index eebcbe6..3d55488 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
@@ -31,7 +31,6 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import java.lang.Exception
/**
* UsbPermissionActivityTest
@@ -54,7 +53,9 @@
putExtra(Intent.EXTRA_INTENT, PendingIntent.getBroadcast(
mContext,
334,
- Intent("NO_ACTION"),
+ Intent("NO_ACTION").apply {
+ setPackage("com.android.systemui.tests")
+ },
PendingIntent.FLAG_MUTABLE))
putExtra(UsbManager.EXTRA_ACCESSORY, UsbAccessory(
"manufacturer",
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
index 46bda06..27d4ea7 100644
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
@@ -21,6 +21,7 @@
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Log;
import android.webkit.PacProcessor;
@@ -33,16 +34,44 @@
public class PacService extends Service {
private static final String TAG = "PacService";
- private Object mLock = new Object();
+ private final Object mLock = new Object();
+ // Webkit PacProcessor cannot be instantiated before the user is unlocked, so this field is
+ // initialized lazily.
@GuardedBy("mLock")
- private final PacProcessor mPacProcessor = PacProcessor.getInstance();
+ private PacProcessor mPacProcessor;
+
+ // Stores PAC script when setPacFile is called before mPacProcessor is available. In case the
+ // script was already fed to the PacProcessor, it should be null.
+ @GuardedBy("mLock")
+ private String mPendingScript;
private ProxyServiceStub mStub = new ProxyServiceStub();
@Override
public void onCreate() {
super.onCreate();
+
+ synchronized (mLock) {
+ checkPacProcessorLocked();
+ }
+ }
+
+ /**
+ * Initializes PacProcessor if it hasn't been initialized yet and if the system user is
+ * unlocked, e.g. after the user has entered their PIN after a reboot.
+ * Returns whether PacProcessor is available.
+ */
+ private boolean checkPacProcessorLocked() {
+ if (mPacProcessor != null) {
+ return true;
+ }
+ UserManager um = getSystemService(UserManager.class);
+ if (um.isUserUnlocked()) {
+ mPacProcessor = PacProcessor.getInstance();
+ return true;
+ }
+ return false;
}
@Override
@@ -74,7 +103,20 @@
}
synchronized (mLock) {
- return mPacProcessor.findProxyForUrl(url);
+ if (checkPacProcessorLocked()) {
+ // Apply pending script in case it was set before processor was ready.
+ if (mPendingScript != null) {
+ if (!mPacProcessor.setProxyScript(mPendingScript)) {
+ Log.e(TAG, "Unable to parse proxy script.");
+ }
+ mPendingScript = null;
+ }
+ return mPacProcessor.findProxyForUrl(url);
+ } else {
+ Log.e(TAG, "PacProcessor isn't ready during early boot,"
+ + " request will be direct");
+ return null;
+ }
}
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Invalid URL was passed");
@@ -88,8 +130,13 @@
throw new SecurityException();
}
synchronized (mLock) {
- if (!mPacProcessor.setProxyScript(script)) {
- Log.e(TAG, "Unable to parse proxy script.");
+ if (checkPacProcessorLocked()) {
+ if (!mPacProcessor.setProxyScript(script)) {
+ Log.e(TAG, "Unable to parse proxy script.");
+ }
+ } else {
+ Log.d(TAG, "PAC processor isn't ready, saving script for later.");
+ mPendingScript = script;
}
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 503b3a9..94bf62f 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1568,17 +1568,23 @@
perm = PackageManager.PERMISSION_DENIED;
}
- if (perm == PackageManager.PERMISSION_GRANTED) {
- skip = true;
- break;
- }
-
int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
if (appOp != AppOpsManager.OP_NONE) {
- if (mService.getAppOpsManager().checkOpNoThrow(appOp,
+ // When there is an app op associated with the permission,
+ // skip when both the permission and the app op are
+ // granted.
+ if ((perm == PackageManager.PERMISSION_GRANTED) && (
+ mService.getAppOpsManager().checkOpNoThrow(appOp,
info.activityInfo.applicationInfo.uid,
info.activityInfo.packageName)
- == AppOpsManager.MODE_ALLOWED) {
+ == AppOpsManager.MODE_ALLOWED)) {
+ skip = true;
+ break;
+ }
+ } else {
+ // When there is no app op associated with the permission,
+ // skip when permission is granted.
+ if (perm == PackageManager.PERMISSION_GRANTED) {
skip = true;
break;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index e09ba34..6552786 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -96,6 +96,7 @@
import android.media.ISpatializerCallback;
import android.media.ISpatializerHeadToSoundStagePoseCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.IVolumeController;
import android.media.MediaMetrics;
@@ -8498,6 +8499,26 @@
mSpatializerHelper.getEffectParameter(key, value);
}
+ /** @see Spatializer#getOutput */
+ public int getSpatializerOutput() {
+ enforceModifyDefaultAudioEffectsPermission();
+ return mSpatializerHelper.getOutput();
+ }
+
+ /** @see Spatializer#setOnSpatializerOutputChangedListener */
+ public void registerSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.registerSpatializerOutputCallback(cb);
+ }
+
+ /** @see Spatializer#clearOnSpatializerOutputChangedListener */
+ public void unregisterSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.unregisterSpatializerOutputCallback(cb);
+ }
+
/**
* post a message to schedule init/release of head tracking sensors
* @param init initialization if true, release if false
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index b2fa86b..7cd027c 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -31,6 +31,7 @@
import android.media.ISpatializerHeadToSoundStagePoseCallback;
import android.media.ISpatializerHeadTrackingCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.SpatializationLevel;
import android.media.Spatializer;
import android.media.SpatializerHeadTrackingMode;
@@ -76,6 +77,7 @@
private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
+ private int mSpatOutput = 0;
private @Nullable ISpatializer mSpat;
private @Nullable SpatializerCallback mSpatCallback;
private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback;
@@ -213,6 +215,18 @@
postInitSensors(true);
}
}
+
+ public void onOutputChanged(int output) {
+ logd("SpatializerCallback.onOutputChanged output:" + output);
+ int oldOutput;
+ synchronized (SpatializerHelper.this) {
+ oldOutput = mSpatOutput;
+ mSpatOutput = output;
+ }
+ if (oldOutput != output) {
+ dispatchOutputUpdate(output);
+ }
+ }
};
// spatializer head tracking callback from native
@@ -782,6 +796,60 @@
}
//------------------------------------------------------
+ // output
+
+ /** @see Spatializer#getOutput */
+ synchronized int getOutput() {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ throw (new IllegalStateException(
+ "Can't get output without a spatializer"));
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ throw (new IllegalStateException(
+ "null Spatializer for getOutput"));
+ }
+ break;
+ }
+ // mSpat != null
+ try {
+ return mSpat.getOutput();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in getOutput", e);
+ return 0;
+ }
+ }
+
+ final RemoteCallbackList<ISpatializerOutputCallback> mOutputCallbacks =
+ new RemoteCallbackList<ISpatializerOutputCallback>();
+
+ synchronized void registerSpatializerOutputCallback(
+ @NonNull ISpatializerOutputCallback callback) {
+ mOutputCallbacks.register(callback);
+ }
+
+ synchronized void unregisterSpatializerOutputCallback(
+ @NonNull ISpatializerOutputCallback callback) {
+ mOutputCallbacks.unregister(callback);
+ }
+
+ private void dispatchOutputUpdate(int output) {
+ final int nbCallbacks = mOutputCallbacks.beginBroadcast();
+ for (int i = 0; i < nbCallbacks; i++) {
+ try {
+ mOutputCallbacks.getBroadcastItem(i).dispatchSpatializerOutputChanged(output);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in dispatchOutputUpdate", e);
+ }
+ }
+ mOutputCallbacks.finishBroadcast();
+ }
+
+ //------------------------------------------------------
// sensors
private void initSensors(boolean init) {
if (mSensorManager == null) {
@@ -825,9 +893,18 @@
}
synchronized void onInitSensors(boolean init) {
- final int[] modes = getSupportedHeadTrackingModes();
- if (modes.length == 0) {
- Log.i(TAG, "not initializing sensors, no headtracking supported");
+ final String action = init ? "initializing" : "releasing";
+ if (mSpat == null) {
+ Log.e(TAG, "not " + action + " sensors, null spatializer");
+ return;
+ }
+ try {
+ if (!mSpat.isHeadTrackingSupported()) {
+ Log.e(TAG, "not " + action + " sensors, spatializer doesn't support headtracking");
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "not " + action + " sensors, error querying headtracking", e);
return;
}
initSensors(init);
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 84be7f5..20687c6 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4073,7 +4073,7 @@
if (hasRestrictedModeAccess(uid)) {
uidBlockedState.allowedReasons |= ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
} else {
- uidBlockedState.allowedReasons &= ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
+ uidBlockedState.allowedReasons &= ~ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
}
uidBlockedState.updateEffectiveBlockedReasons();
if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index ed1f5f5..3fc4169 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -356,7 +356,7 @@
return false;
}
- if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 1000) {
+ if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 5000) {
return false;
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 3019146..7096f6f 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -913,6 +913,11 @@
}
grantPermissionsToSystemPackage(pm, dialerPackage, userId,
CONTACTS_PERMISSIONS, SMS_PERMISSIONS, MICROPHONE_PERMISSIONS, CAMERA_PERMISSIONS);
+ boolean isAndroidAutomotive =
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0);
+ if (isAndroidAutomotive) {
+ grantPermissionsToSystemPackage(pm, dialerPackage, userId, NEARBY_DEVICES_PERMISSIONS);
+ }
}
private void grantDefaultPermissionsToDefaultSystemSmsApp(PackageManagerWrapper pm,
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 2a47512..7307662 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1319,24 +1319,18 @@
return IExternalVibratorService.SCALE_MUTE;
}
- int mode = checkAppOpModeLocked(vib.getUid(), vib.getPackage(),
- vib.getVibrationAttributes());
- if (mode != AppOpsManager.MODE_ALLOWED) {
- ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
- vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
- if (mode == AppOpsManager.MODE_ERRORED) {
- Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
- endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
- } else {
- endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
- }
- return vibHolder.scale;
- }
-
ExternalVibrationHolder cancelingExternalVibration = null;
VibrationThread cancelingVibration = null;
int scale;
synchronized (mLock) {
+ Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
+ vib.getUid(), vib.getPackage(), vib.getVibrationAttributes());
+ if (ignoreStatus != null) {
+ ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
+ vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
+ endVibrationLocked(vibHolder, ignoreStatus);
+ return vibHolder.scale;
+ }
if (mCurrentExternalVibration != null
&& mCurrentExternalVibration.externalVibration.equals(vib)) {
// We are already playing this external vibration, so we can return the same
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 394ff75..8ef973d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -46,8 +46,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
-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;
@@ -2322,7 +2320,8 @@
// 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) {
+ || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING
+ || mStartingWindow == null) {
if (parcelable != null) {
parcelable.clearIfNeeded();
}
@@ -2331,13 +2330,17 @@
return;
}
// schedule attach splashScreen to client
+ final SurfaceControl windowAnimationLeash = TaskOrganizerController
+ .applyStartingWindowAnimation(mStartingWindow);
try {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
- TransferSplashScreenViewStateItem.obtain(ATTACH_TO, parcelable));
+ TransferSplashScreenViewStateItem.obtain(parcelable,
+ windowAnimationLeash));
scheduleTransferSplashScreenTimeout();
} catch (Exception e) {
Slog.w(TAG, "onCopySplashScreenComplete fail: " + this);
+ mStartingWindow.cancelAnimation();
parcelable.clearIfNeeded();
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
}
@@ -2347,14 +2350,9 @@
removeTransferSplashScreenTimeout();
// Client has draw the splash screen, so we can remove the starting window.
if (mStartingWindow != null) {
+ mStartingWindow.cancelAnimation();
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;
removeStartingWindowAnimation(false /* prepareAnimation */);
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index b71ad2e..6ad2f7c 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -501,6 +501,8 @@
int startActivityInTaskFragment(@NonNull TaskFragment taskFragment,
@NonNull Intent activityIntent, @Nullable Bundle activityOptions,
@Nullable IBinder resultTo) {
+ final ActivityRecord caller =
+ resultTo != null ? ActivityRecord.forTokenLocked(resultTo) : null;
return obtainStarter(activityIntent, "startActivityInTaskFragment")
.setActivityOptions(activityOptions)
.setInTaskFragment(taskFragment)
@@ -508,6 +510,7 @@
.setRequestCode(-1)
.setCallingUid(Binder.getCallingUid())
.setCallingPid(Binder.getCallingPid())
+ .setUserId(caller != null ? caller.mUserId : mService.getCurrentUserId())
.execute();
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 1707895..47467abf 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1979,7 +1979,7 @@
// Allowing the embedding if the task is owned by system.
final int hostUid = hostTask.effectiveUid;
- if (hostUid == Process.SYSTEM_UID) {
+ if (UserHandle.getAppId(hostUid) == Process.SYSTEM_UID) {
return true;
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index ffaf710..535a061 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -124,6 +124,7 @@
@interface TransitContainerType {}
private final ArrayMap<WindowContainer, Integer> mTempTransitionReasons = new ArrayMap<>();
+ private final ArrayList<WindowContainer> mTempTransitionWindows = new ArrayList<>();
AppTransitionController(WindowManagerService service, DisplayContent displayContent) {
mService = service;
@@ -523,26 +524,44 @@
}
}
+ private boolean transitionMayContainNonAppWindows(@TransitionOldType int transit) {
+ // We don't want to have the client to animate any non-app windows.
+ // Having {@code transit} of those types doesn't mean it will contain non-app windows, but
+ // non-app windows will only be included with those transition types. And we don't currently
+ // have any use case of those for TaskFragment transition.
+ // @see NonAppWindowAnimationAdapter#startNonAppWindowAnimations
+ if (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
+ || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER
+ || transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT
+ || transit == TRANSIT_OLD_WALLPAPER_CLOSE) {
+ return true;
+ }
+
+ // Check if the wallpaper is going to participate in the transition. We don't want to have
+ // the client to animate the wallpaper windows.
+ // @see WallpaperAnimationAdapter#startWallpaperAnimations
+ return mDisplayContent.mWallpaperController.isWallpaperVisible();
+ }
+
/**
- * Overrides the pending transition with the remote animation defined by the
- * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
- * {@link TaskFragment} that are organized by the same organizer.
- *
- * @return {@code true} if the transition is overridden.
+ * Finds the common {@link android.window.TaskFragmentOrganizer} that organizes all app windows
+ * in the current transition.
+ * @return {@code null} if there is no such organizer, or if there are more than one.
*/
- private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
- ArraySet<Integer> activityTypes) {
- final ArrayList<WindowContainer> allWindows = new ArrayList<>();
- allWindows.addAll(mDisplayContent.mClosingApps);
- allWindows.addAll(mDisplayContent.mOpeningApps);
- allWindows.addAll(mDisplayContent.mChangingContainers);
+ @Nullable
+ private ITaskFragmentOrganizer findTaskFragmentOrganizerForAllWindows() {
+ mTempTransitionWindows.clear();
+ mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
+ mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
+ mTempTransitionWindows.addAll(mDisplayContent.mChangingContainers);
// It should only animated by the organizer if all windows are below the same leaf Task.
Task leafTask = null;
- for (int i = allWindows.size() - 1; i >= 0; i--) {
- final ActivityRecord r = getAppFromContainer(allWindows.get(i));
+ for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = getAppFromContainer(mTempTransitionWindows.get(i));
if (r == null) {
- return false;
+ leafTask = null;
+ break;
}
// The activity may be a child of embedded Task, but we want to find the owner Task.
// As a result, find the organized TaskFragment first.
@@ -561,26 +580,31 @@
? organizedTaskFragment.getTask()
: r.getTask();
if (task == null) {
- return false;
+ leafTask = null;
+ break;
}
// We don't want the organizer to handle transition of other non-embedded Task.
if (leafTask != null && leafTask != task) {
- return false;
+ leafTask = null;
+ break;
}
final ActivityRecord rootActivity = task.getRootActivity();
// We don't want the organizer to handle transition when the whole app is closing.
if (rootActivity == null) {
- return false;
+ leafTask = null;
+ break;
}
// We don't want the organizer to handle transition of non-embedded activity of other
// app.
if (r.getUid() != rootActivity.getUid() && !r.isEmbedded()) {
- return false;
+ leafTask = null;
+ break;
}
leafTask = task;
}
+ mTempTransitionWindows.clear();
if (leafTask == null) {
- return false;
+ return null;
}
// We don't support remote animation for Task with multiple TaskFragmentOrganizers.
@@ -599,12 +623,28 @@
if (hasMultipleOrganizers) {
ProtoLog.e(WM_DEBUG_APP_TRANSITIONS, "We don't support remote animation for"
+ " Task with multiple TaskFragmentOrganizers.");
+ return null;
+ }
+ return organizer[0];
+ }
+
+ /**
+ * Overrides the pending transition with the remote animation defined by the
+ * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
+ * {@link TaskFragment} that are organized by the same organizer.
+ *
+ * @return {@code true} if the transition is overridden.
+ */
+ private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
+ ArraySet<Integer> activityTypes) {
+ if (transitionMayContainNonAppWindows(transit)) {
return false;
}
- final RemoteAnimationDefinition definition = organizer[0] != null
+ final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizerForAllWindows();
+ final RemoteAnimationDefinition definition = organizer != null
? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
- .getRemoteAnimationDefinition(organizer[0])
+ .getRemoteAnimationDefinition(organizer)
: null;
final RemoteAnimationAdapter adapter = definition != null
? definition.getAdapter(transit, activityTypes)
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ff4dd46..aaca58e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -91,6 +91,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LAYER_MIRRORING;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
@@ -127,7 +128,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -588,7 +588,7 @@
/** Caches the value whether told display manager that we have content. */
private boolean mLastHasContent;
- private DisplayRotationUtil mRotationUtil = new DisplayRotationUtil();
+ private static DisplayRotationUtil sRotationUtil = new DisplayRotationUtil();
/**
* The input method window for this display.
@@ -1003,8 +1003,8 @@
final boolean committed = winAnimator.commitFinishDrawingLocked();
if (isDefaultDisplay && committed) {
if (w.hasWallpaper()) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "First draw done in potential wallpaper target " + w);
+ ProtoLog.v(WM_DEBUG_WALLPAPER,
+ "First draw done in potential wallpaper target %s", w);
mWallpaperMayChange = true;
pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) {
@@ -2053,29 +2053,35 @@
return mDisplayCutoutCache.getOrCompute(mInitialDisplayCutout, rotation);
}
- private WmDisplayCutout calculateDisplayCutoutForRotationUncached(
- DisplayCutout cutout, int rotation) {
+ static WmDisplayCutout calculateDisplayCutoutForRotationAndDisplaySizeUncached(
+ DisplayCutout cutout, int rotation, int displayWidth, int displayHeight) {
if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
return WmDisplayCutout.NO_CUTOUT;
}
if (rotation == ROTATION_0) {
return WmDisplayCutout.computeSafeInsets(
- cutout, mInitialDisplayWidth, mInitialDisplayHeight);
+ cutout, displayWidth, displayHeight);
}
final Insets waterfallInsets =
RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final Rect[] newBounds = mRotationUtil.getRotatedBounds(
+ final Rect[] newBounds = sRotationUtil.getRotatedBounds(
cutout.getBoundingRectsAll(),
- rotation, mInitialDisplayWidth, mInitialDisplayHeight);
+ rotation, displayWidth, displayHeight);
final CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
final CutoutPathParserInfo newInfo = new CutoutPathParserInfo(
info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
info.getCutoutSpec(), rotation, info.getScale());
return WmDisplayCutout.computeSafeInsets(
DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
- rotated ? mInitialDisplayHeight : mInitialDisplayWidth,
- rotated ? mInitialDisplayWidth : mInitialDisplayHeight);
+ rotated ? displayHeight : displayWidth,
+ rotated ? displayWidth : displayHeight);
+ }
+
+ private WmDisplayCutout calculateDisplayCutoutForRotationUncached(
+ DisplayCutout cutout, int rotation) {
+ return calculateDisplayCutoutForRotationAndDisplaySizeUncached(cutout, rotation,
+ mInitialDisplayWidth, mInitialDisplayHeight);
}
RoundedCorners calculateRoundedCornersForRotation(int rotation) {
@@ -5156,9 +5162,7 @@
onAppTransitionDone();
changes |= FINISH_LAYOUT_REDO_LAYOUT;
- if (DEBUG_WALLPAPER_LIGHT) {
- Slog.v(TAG_WM, "Wallpaper layer changed: assigning layers + relayout");
- }
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper layer changed: assigning layers + relayout");
computeImeTarget(true /* updateImeTarget */);
mWallpaperMayChange = true;
// Since the window list has been rebuilt, focus might have to be recomputed since the
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ccfb174..a7c496e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1530,31 +1530,51 @@
* some temporal states, but doesn't change the window frames used to show on screen.
*/
void simulateLayoutDisplay(DisplayFrames displayFrames, SparseArray<Rect> barContentFrames) {
- if (mNavigationBar != null) {
- final WindowFrames simulatedWindowFrames = new WindowFrames();
- if (INSETS_LAYOUT_GENERALIZATION) {
- simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames,
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ final InsetsStateController insetsStateController =
+ mDisplayContent.getInsetsStateController();
+ for (int type = 0; type < InsetsState.SIZE; type++) {
+ final InsetsSourceProvider provider =
+ insetsStateController.peekSourceProvider(type);
+ if (provider == null || !provider.hasWindow()
+ || provider.mWin.getControllableInsetProvider() != provider) {
+ continue;
+ }
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
+ simulateLayoutDecorWindow(provider.mWin, displayFrames, simulatedWindowFrames,
barContentFrames,
contentFrame -> simulateLayoutForContentFrame(displayFrames,
- mNavigationBar, contentFrame));
- } else {
+ provider.mWin, contentFrame));
+ }
+ } else {
+ if (mNavigationBar != null) {
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames,
barContentFrames, contentFrame -> layoutNavigationBar(displayFrames,
contentFrame));
}
- }
- if (mStatusBar != null) {
- final WindowFrames simulatedWindowFrames = new WindowFrames();
- if (INSETS_LAYOUT_GENERALIZATION) {
- simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames,
- barContentFrames,
- contentFrame -> simulateLayoutForContentFrame(displayFrames,
- mStatusBar, contentFrame));
- } else {
+ if (mStatusBar != null) {
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames,
barContentFrames,
contentFrame -> layoutStatusBar(displayFrames, contentFrame));
}
+ if (mExtraNavBarAlt != null) {
+ // There's no pre-defined behavior for the extra navigation bar, we need to use the
+ // new flexible insets logic anyway.
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
+ simulateLayoutDecorWindow(mExtraNavBarAlt, displayFrames, simulatedWindowFrames,
+ barContentFrames,
+ contentFrame -> simulateLayoutForContentFrame(displayFrames,
+ mExtraNavBarAlt, contentFrame));
+ }
+ if (mClimateBarAlt != null) {
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
+ simulateLayoutDecorWindow(mClimateBarAlt, displayFrames, simulatedWindowFrames,
+ barContentFrames,
+ contentFrame -> simulateLayoutForContentFrame(displayFrames,
+ mClimateBarAlt, contentFrame));
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index c630e91..bd41de3 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -194,8 +194,7 @@
if (keyguardChanged) {
// Irrelevant to AOD.
- dismissMultiWindowModeForTaskIfNeeded(null /* currentTaskControllsingOcclusion */,
- false /* turningScreenOn */);
+ dismissMultiWindowModeForTaskIfNeeded(null /* currentTaskControllsingOcclusion */);
mKeyguardGoingAway = false;
if (keyguardShowing) {
mDismissalRequested = false;
@@ -396,6 +395,8 @@
mService.continueWindowLayout();
}
}
+ dismissMultiWindowModeForTaskIfNeeded(topActivity != null
+ ? topActivity.getRootTask() : null);
}
/**
@@ -421,21 +422,6 @@
}
}
- /**
- * Called when somebody wants to turn screen on.
- */
- private void handleTurnScreenOn(int displayId) {
- if (displayId != DEFAULT_DISPLAY) {
- return;
- }
-
- mTaskSupervisor.wakeUp("handleTurnScreenOn");
- if (mKeyguardShowing && canDismissKeyguard()) {
- mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
- mDismissalRequested = true;
- }
- }
-
boolean isDisplayOccluded(int displayId) {
return getDisplayState(displayId).mOccluded;
}
@@ -449,11 +435,9 @@
}
private void dismissMultiWindowModeForTaskIfNeeded(
- @Nullable Task currentTaskControllingOcclusion, boolean turningScreenOn) {
- // If turningScreenOn is true, it means that the visibility state has changed from
- // currentTaskControllingOcclusion and we should update windowing mode.
+ @Nullable Task currentTaskControllingOcclusion) {
// TODO(b/113840485): Handle docked stack for individual display.
- if (!turningScreenOn && (!mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY))) {
+ if (!mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY)) {
return;
}
@@ -592,26 +576,17 @@
&& controller.mWindowManager.isKeyguardSecure(
controller.mService.getCurrentUserId());
- boolean occludingChange = false;
- boolean turningScreenOn = false;
if (mTopTurnScreenOnActivity != lastTurnScreenOnActivity
&& mTopTurnScreenOnActivity != null
&& !mService.mWindowManager.mPowerManager.isInteractive()
- && (mRequestDismissKeyguard || occludedByActivity
- || controller.canDismissKeyguard())) {
- turningScreenOn = true;
- controller.handleTurnScreenOn(mDisplayId);
+ && (mRequestDismissKeyguard || occludedByActivity)) {
+ controller.mTaskSupervisor.wakeUp("handleTurnScreenOn");
mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
}
if (lastOccluded != mOccluded) {
- occludingChange = true;
controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
}
-
- if (occludingChange || turningScreenOn) {
- controller.dismissMultiWindowModeForTaskIfNeeded(task, turningScreenOn);
- }
}
/**
diff --git a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
index ef8dee4..11a27c5 100644
--- a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
+++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
@@ -120,8 +120,15 @@
@Surface.Rotation int rotation) {
DisplayInfo updatedDisplayInfo = new DisplayInfo();
updatedDisplayInfo.copyFrom(displayInfo);
- updatedDisplayInfo.rotation = rotation;
+ // Apply rotations before updating width and height
+ updatedDisplayInfo.roundedCorners = updatedDisplayInfo.roundedCorners.rotate(rotation,
+ updatedDisplayInfo.logicalWidth, updatedDisplayInfo.logicalHeight);
+ updatedDisplayInfo.displayCutout =
+ DisplayContent.calculateDisplayCutoutForRotationAndDisplaySizeUncached(
+ updatedDisplayInfo.displayCutout, rotation, updatedDisplayInfo.logicalWidth,
+ updatedDisplayInfo.logicalHeight).getDisplayCutout();
+ updatedDisplayInfo.rotation = rotation;
final int naturalWidth = updatedDisplayInfo.getNaturalWidth();
final int naturalHeight = updatedDisplayInfo.getNaturalHeight();
updatedDisplayInfo.logicalWidth = naturalWidth;
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index ba1cf8a..ee05523 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -170,6 +170,13 @@
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "startRecentsActivity(): intent=%s", mTargetIntent);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RecentsAnimation#startRecentsActivity");
+ // Cancel any existing recents animation running synchronously (do not hold the
+ // WM lock) before starting the newly requested recents animation as they can not coexist
+ if (mWindowManager.getRecentsAnimationController() != null) {
+ mWindowManager.getRecentsAnimationController().forceCancelAnimation(
+ REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity");
+ }
+
// If the activity is associated with the root recents task, then try and get that first
Task targetRootTask = mDefaultTaskDisplayArea.getRootTask(WINDOWING_MODE_UNDEFINED,
mTargetActivityType);
@@ -243,12 +250,7 @@
targetActivity.intent.replaceExtras(mTargetIntent);
// Fetch all the surface controls and pass them to the client to get the animation
- // started. Cancel any existing recents animation running synchronously (do not hold the
- // WM lock)
- if (mWindowManager.getRecentsAnimationController() != null) {
- mWindowManager.getRecentsAnimationController().forceCancelAnimation(
- REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity");
- }
+ // started
mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
this, mDefaultTaskDisplayArea.getDisplayId(),
mTaskSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity);
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index a663c62..22c8459 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -793,7 +793,7 @@
private RemoteAnimationTarget[] createWallpaperAnimations() {
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "createWallpaperAnimations()");
- return WallpaperAnimationAdapter.startWallpaperAnimations(mService, 0L, 0L,
+ return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent, 0L, 0L,
adapter -> {
synchronized (mService.mGlobalLock) {
// If the wallpaper animation is canceled, continue with the recents
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 16a45fe..ca1aed5 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -207,7 +207,7 @@
if (wrappers.mThumbnailAdapter != null
&& wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) {
wrappers.mThumbnailAdapter.mCapturedFinishCallback
- .onAnimationFinished(wrappers.mAdapter.mAnimationType,
+ .onAnimationFinished(wrappers.mThumbnailAdapter.mAnimationType,
wrappers.mThumbnailAdapter);
}
mPendingAnimations.remove(i);
@@ -218,7 +218,7 @@
private RemoteAnimationTarget[] createWallpaperAnimations() {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createWallpaperAnimations()");
- return WallpaperAnimationAdapter.startWallpaperAnimations(mService,
+ return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent,
mRemoteAnimationAdapter.getDuration(),
mRemoteAnimationAdapter.getStatusBarTransitionDelay(),
adapter -> {
@@ -260,7 +260,7 @@
}
if (adapters.mThumbnailAdapter != null) {
adapters.mThumbnailAdapter.mCapturedFinishCallback
- .onAnimationFinished(adapters.mAdapter.mAnimationType,
+ .onAnimationFinished(adapters.mThumbnailAdapter.mAnimationType,
adapters.mThumbnailAdapter);
}
mPendingAnimations.remove(i);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 1a881f7..e3600e6 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -48,6 +48,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
@@ -72,6 +73,7 @@
import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG;
+import static com.android.server.wm.RootWindowContainerProto.DEFAULT_MIN_SIZE_RESIZABLE_TASK;
import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
@@ -79,7 +81,6 @@
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -894,7 +895,7 @@
for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
if (displayContent.mWallpaperMayChange) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Wallpaper may change! Adjusting");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper may change! Adjusting");
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) {
surfacePlacer.debugLayoutRepeats("WallpaperMayChange",
@@ -1254,6 +1255,11 @@
pw.println(mTopFocusedDisplayId);
}
+ void dumpDefaultMinSizeOfResizableTask(PrintWriter pw) {
+ pw.print(" mDefaultMinSizeOfResizeableTaskDp=");
+ pw.println(mDefaultMinSizeOfResizeableTaskDp);
+ }
+
void dumpLayoutNeededDisplayIds(PrintWriter pw) {
if (!isLayoutNeeded()) {
return;
@@ -1300,7 +1306,7 @@
mTaskSupervisor.getKeyguardController().dumpDebug(proto, KEYGUARD_CONTROLLER);
proto.write(IS_HOME_RECENTS_COMPONENT,
mTaskSupervisor.mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser));
-
+ proto.write(DEFAULT_MIN_SIZE_RESIZABLE_TASK, mDefaultMinSizeOfResizeableTaskDp);
proto.end(token);
}
@@ -2151,6 +2157,9 @@
final Task rootTask;
if (singleActivity) {
rootTask = task;
+
+ // Apply the last recents animation leash transform to the task entering PIP
+ rootTask.maybeApplyLastRecentsAnimationTransaction();
} else {
// In the case of multiple activities, we will create a new task for it and then
// move the PIP activity into the task. Note that we explicitly defer the task
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index c7bf8ec..d712bbf 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -57,8 +57,11 @@
@VisibleForTesting
SurfaceControl mLeash;
@VisibleForTesting
+ SurfaceFreezer.Snapshot mSnapshot;
+ @VisibleForTesting
final Animatable mAnimatable;
- private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
+ @VisibleForTesting
+ final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
/**
* Static callback to run on all animations started through this SurfaceAnimator
@@ -151,12 +154,14 @@
* @param animationFinishedCallback The callback being triggered when the animation finishes.
* @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a
* cancel call to the underlying AnimationAdapter.
+ * @param snapshotAnim The animation to run for the snapshot. {@code null} if there is no
+ * snapshot.
*/
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
@Nullable Runnable animationCancelledCallback,
- @Nullable SurfaceFreezer freezer) {
+ @Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) {
cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
mAnimation = anim;
mAnimationType = type;
@@ -181,12 +186,16 @@
return;
}
mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
+ if (snapshotAnim != null) {
+ mSnapshot = freezer.takeSnapshotForAnimation();
+ mSnapshot.startAnimation(t, snapshotAnim, type);
+ }
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type) {
startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */,
- null /* animationCancelledCallback */, null /* freezer */);
+ null /* animationCancelledCallback */, null /* snapshotAnim */, null /* freezer */);
}
/**
@@ -328,6 +337,7 @@
final OnAnimationFinishedCallback animationFinishedCallback =
mSurfaceAnimationFinishedCallback;
final Runnable animationCancelledCallback = mAnimationCancelledCallback;
+ final SurfaceFreezer.Snapshot snapshot = mSnapshot;
reset(t, false);
if (animation != null) {
if (!mAnimationStartDelayed && forwardCancel) {
@@ -346,9 +356,14 @@
}
}
- if (forwardCancel && leash != null) {
- t.remove(leash);
- mService.scheduleAnimationLocked();
+ if (forwardCancel) {
+ if (snapshot != null) {
+ snapshot.cancelAnimation(t, false /* restarting */);
+ }
+ if (leash != null) {
+ t.remove(leash);
+ mService.scheduleAnimationLocked();
+ }
}
if (!restarting) {
@@ -361,6 +376,12 @@
mAnimation = null;
mSurfaceAnimationFinishedCallback = null;
mAnimationType = ANIMATION_TYPE_NONE;
+ final SurfaceFreezer.Snapshot snapshot = mSnapshot;
+ mSnapshot = null;
+ if (snapshot != null) {
+ // Reset the mSnapshot reference before calling the callback to prevent circular reset.
+ snapshot.cancelAnimation(t, !destroyLeash);
+ }
if (mLeash == null) {
return;
}
@@ -377,11 +398,15 @@
boolean scheduleAnim = false;
final SurfaceControl surface = animatable.getSurfaceControl();
final SurfaceControl parent = animatable.getParentSurfaceControl();
+ final SurfaceControl curAnimationLeash = animatable.getAnimationLeash();
// If the surface was destroyed or the leash is invalid, we don't care to reparent it back.
// Note that we also set this variable to true even if the parent isn't valid anymore, in
// order to ensure onAnimationLeashLost still gets called in this case.
- final boolean reparent = surface != null;
+ // If the animation leash is set, and it is different from the removing leash, it means the
+ // surface now has a new animation surface. We don't want to reparent for that.
+ final boolean reparent = surface != null && (curAnimationLeash == null
+ || curAnimationLeash.equals(leash));
if (reparent) {
if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent: " + parent);
// We shouldn't really need these isValid checks but we do
@@ -608,6 +633,14 @@
void onAnimationLeashLost(Transaction t);
/**
+ * Gets the last created animation leash that has not lost yet.
+ */
+ @Nullable
+ default SurfaceControl getAnimationLeash() {
+ return null;
+ }
+
+ /**
* @return A new surface to be used for the animation leash, inserted at the correct
* position in the hierarchy.
*/
diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java
index 89986ce..9a0cabe 100644
--- a/services/core/java/com/android/server/wm/SurfaceFreezer.java
+++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
import android.annotation.NonNull;
@@ -27,13 +26,11 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
-import android.view.Surface;
import android.view.SurfaceControl;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import java.util.function.Supplier;
-
/**
* This class handles "freezing" of an Animatable. The Animatable in question should implement
* Freezable.
@@ -54,7 +51,8 @@
private final Freezable mAnimatable;
private final WindowManagerService mWmService;
- private SurfaceControl mLeash;
+ @VisibleForTesting
+ SurfaceControl mLeash;
Snapshot mSnapshot = null;
final Rect mFreezeBounds = new Rect();
@@ -94,7 +92,7 @@
if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
return;
}
- mSnapshot = new Snapshot(mWmService.mSurfaceFactory, t, screenshotBuffer, mLeash);
+ mSnapshot = new Snapshot(t, screenshotBuffer, mLeash);
}
}
@@ -109,12 +107,25 @@
}
/**
+ * Used by {@link SurfaceAnimator}. This "transfers" the snapshot leash to be used for
+ * animation. By transferring the leash, this will no longer try to clean-up the leash when
+ * finished.
+ */
+ @Nullable
+ Snapshot takeSnapshotForAnimation() {
+ final Snapshot out = mSnapshot;
+ mSnapshot = null;
+ return out;
+ }
+
+ /**
* Clean-up the snapshot and remove leash. If the leash was taken, this just cleans-up the
* snapshot.
*/
void unfreeze(SurfaceControl.Transaction t) {
if (mSnapshot != null) {
mSnapshot.cancelAnimation(t, false /* restarting */);
+ mSnapshot = null;
}
if (mLeash == null) {
return;
@@ -163,13 +174,12 @@
class Snapshot {
private SurfaceControl mSurfaceControl;
private AnimationAdapter mAnimation;
- private SurfaceAnimator.OnAnimationFinishedCallback mFinishedCallback;
/**
* @param t Transaction to create the thumbnail in.
* @param screenshotBuffer A thumbnail or placeholder for thumbnail to initialize with.
*/
- Snapshot(Supplier<Surface> surfaceFactory, SurfaceControl.Transaction t,
+ Snapshot(SurfaceControl.Transaction t,
SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer, SurfaceControl parent) {
// We can't use a delegating constructor since we need to
// reference this::onAnimationFinished
@@ -211,19 +221,15 @@
* component responsible for running the animation. It runs the animation with
* {@link AnimationAdapter#startAnimation} once the hierarchy with
* the Leash has been set up.
- * @param animationFinishedCallback The callback being triggered when the animation
- * finishes.
*/
- void startAnimation(SurfaceControl.Transaction t, AnimationAdapter anim, int type,
- @Nullable SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback) {
+ void startAnimation(SurfaceControl.Transaction t, AnimationAdapter anim, int type) {
cancelAnimation(t, true /* restarting */);
mAnimation = anim;
- mFinishedCallback = animationFinishedCallback;
if (mSurfaceControl == null) {
cancelAnimation(t, false /* restarting */);
return;
}
- mAnimation.startAnimation(mSurfaceControl, t, type, animationFinishedCallback);
+ mAnimation.startAnimation(mSurfaceControl, t, type, null /* finishCallback */);
}
/**
@@ -235,18 +241,9 @@
void cancelAnimation(SurfaceControl.Transaction t, boolean restarting) {
final SurfaceControl leash = mSurfaceControl;
final AnimationAdapter animation = mAnimation;
- final SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback =
- mFinishedCallback;
mAnimation = null;
- mFinishedCallback = null;
if (animation != null) {
animation.onAnimationCancelled(leash);
- if (!restarting) {
- if (animationFinishedCallback != null) {
- animationFinishedCallback.onAnimationFinished(
- ANIMATION_TYPE_APP_TRANSITION, animation);
- }
- }
}
if (!restarting) {
destroy(t);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3cb80d6..8fbd7f9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1926,6 +1926,7 @@
final ActivityRecord r = topRunningActivity();
if (r != null && mDisplayContent.isFixedRotationLaunchingApp(r)) {
getSyncTransaction().setWindowCrop(mSurfaceControl, null)
+ .setCornerRadius(mSurfaceControl, 0f)
.setMatrix(mSurfaceControl, Matrix.IDENTITY_MATRIX, new float[9]);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index e31a662..d543c1f 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -717,7 +717,7 @@
}
// First we get the default size we want.
- getDefaultFreeformSize(displayArea, layout, orientation, mTmpBounds);
+ getDefaultFreeformSize(root.info, displayArea, layout, orientation, mTmpBounds);
if (hasInitialBounds || sizeMatches(inOutBounds, mTmpBounds)) {
// We're here because either input parameters specified initial bounds, or the suggested
// bounds have the same size of the default freeform size. We should use the suggested
@@ -785,7 +785,8 @@
return orientation;
}
- private void getDefaultFreeformSize(@NonNull TaskDisplayArea displayArea,
+ private void getDefaultFreeformSize(@NonNull ActivityInfo info,
+ @NonNull TaskDisplayArea displayArea,
@NonNull ActivityInfo.WindowLayout layout, int orientation, @NonNull Rect bounds) {
// Default size, which is letterboxing/pillarboxing in displayArea. That's to say the large
// dimension of default size is the small dimension of displayArea size, and the small
@@ -816,11 +817,38 @@
final int layoutMinWidth = (layout == null) ? -1 : layout.minWidth;
final int layoutMinHeight = (layout == null) ? -1 : layout.minHeight;
- // Final result.
+ // Aspect ratio requirements.
+ final float minAspectRatio = info.getMinAspectRatio();
+ final float maxAspectRatio = info.getMaxAspectRatio();
+
final int width = Math.min(defaultWidth, Math.max(phoneWidth, layoutMinWidth));
final int height = Math.min(defaultHeight, Math.max(phoneHeight, layoutMinHeight));
+ final float aspectRatio = (float) Math.max(width, height) / (float) Math.min(width, height);
- bounds.set(0, 0, width, height);
+ // Adjust the width and height to the aspect ratio requirements.
+ int adjWidth = width;
+ int adjHeight = height;
+ if (minAspectRatio >= 1 && aspectRatio < minAspectRatio) {
+ // The aspect ratio is below the minimum, adjust it to the minimum.
+ if (orientation == SCREEN_ORIENTATION_LANDSCAPE) {
+ // Fix the width, scale the height.
+ adjHeight = (int) (adjWidth / minAspectRatio + 0.5f);
+ } else {
+ // Fix the height, scale the width.
+ adjWidth = (int) (adjHeight / minAspectRatio + 0.5f);
+ }
+ } else if (maxAspectRatio >= 1 && aspectRatio > maxAspectRatio) {
+ // The aspect ratio exceeds the maximum, adjust it to the maximum.
+ if (orientation == SCREEN_ORIENTATION_LANDSCAPE) {
+ // Fix the width, scale the height.
+ adjHeight = (int) (adjWidth / maxAspectRatio + 0.5f);
+ } else {
+ // Fix the height, scale the width.
+ adjWidth = (int) (adjHeight / maxAspectRatio + 0.5f);
+ }
+ }
+
+ bounds.set(0, 0, adjWidth, adjHeight);
bounds.offset(stableBounds.left, stableBounds.top);
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index f1345e8..731036b 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -422,8 +422,8 @@
}
// Capture the animation surface control for activity's main window
- private static class StartingWindowAnimationAdaptor implements AnimationAdapter {
- private SurfaceControl mAnimationLeash;
+ static class StartingWindowAnimationAdaptor implements AnimationAdapter {
+ SurfaceControl mAnimationLeash;
@Override
public boolean getShowWallpaper() {
return false;
@@ -464,6 +464,13 @@
}
}
+ static SurfaceControl applyStartingWindowAnimation(WindowContainer window) {
+ final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor();
+ window.startAnimation(window.getPendingTransaction(), adaptor, false,
+ ANIMATION_TYPE_STARTING_REVEAL);
+ return adaptor.mAnimationLeash;
+ }
+
boolean addStartingWindow(Task task, ActivityRecord activity, int launchTheme,
TaskSnapshot taskSnapshot) {
final Task rootTask = task.getRootTask();
@@ -510,12 +517,8 @@
final WindowState mainWindow =
topActivity.findMainWindow(false/* includeStartingApp */);
if (mainWindow != null) {
- final StartingWindowAnimationAdaptor adaptor =
- new StartingWindowAnimationAdaptor();
final SurfaceControl.Transaction t = mainWindow.getPendingTransaction();
- mainWindow.startAnimation(t, adaptor, false,
- ANIMATION_TYPE_STARTING_REVEAL);
- removalInfo.windowAnimationLeash = adaptor.mAnimationLeash;
+ removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
removalInfo.mainFrame = mainWindow.getRelativeFrame();
t.setPosition(removalInfo.windowAnimationLeash,
removalInfo.mainFrame.left, removalInfo.mainFrame.top);
diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
index 25f7269..0b20f37 100644
--- a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
@@ -64,18 +64,17 @@
*
* @return RemoteAnimationTarget[] targets for all the visible wallpaper windows
*/
- public static RemoteAnimationTarget[] startWallpaperAnimations(WindowManagerService service,
+ public static RemoteAnimationTarget[] startWallpaperAnimations(DisplayContent displayContent,
long durationHint, long statusBarTransitionDelay,
Consumer<WallpaperAnimationAdapter> animationCanceledRunnable,
ArrayList<WallpaperAnimationAdapter> adaptersOut) {
+ if (!displayContent.mWallpaperController.isWallpaperVisible()) {
+ ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
+ "\tWallpaper of display=%s is not visible", displayContent);
+ return new RemoteAnimationTarget[0];
+ }
final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
- service.mRoot.forAllWallpaperWindows(wallpaperWindow -> {
- if (!wallpaperWindow.getDisplayContent().mWallpaperController.isWallpaperVisible()) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tNot visible=%s", wallpaperWindow);
- return;
- }
-
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tvisible=%s", wallpaperWindow);
+ displayContent.forAllWallpaperWindows(wallpaperWindow -> {
final WallpaperAnimationAdapter wallpaperAdapter = new WallpaperAnimationAdapter(
wallpaperWindow, durationHint, statusBarTransitionDelay,
animationCanceledRunnable);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 0909462..a92e088 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -24,12 +24,12 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT;
@@ -49,6 +49,8 @@
import android.view.animation.Animation;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogImpl;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import java.io.PrintWriter;
@@ -291,10 +293,11 @@
for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
final WallpaperWindowToken token = mWallpaperTokens.get(i);
token.setVisibility(false);
- if (DEBUG_WALLPAPER_LIGHT && token.isVisible()) {
- Slog.d(TAG, "Hiding wallpaper " + token
- + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev="
- + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, " "));
+ if (ProtoLogImpl.isEnabled(WM_DEBUG_WALLPAPER) && token.isVisible()) {
+ ProtoLog.d(WM_DEBUG_WALLPAPER,
+ "Hiding wallpaper %s from %s target=%s prev=%s callers=%s",
+ token, winGoingAway, mWallpaperTarget, mPrevWallpaperTarget,
+ Debug.getCallers(5));
}
}
}
@@ -544,15 +547,15 @@
// Is it time to stop animating?
if (!mPrevWallpaperTarget.isAnimatingLw()) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "No longer animating wallpaper targets!");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "No longer animating wallpaper targets!");
mPrevWallpaperTarget = null;
mWallpaperTarget = wallpaperTarget;
}
return;
}
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "New wallpaper target: " + wallpaperTarget + " prevTarget: " + mWallpaperTarget);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "New wallpaper target: %s prevTarget: %s caller=%s",
+ wallpaperTarget, mWallpaperTarget, Debug.getCallers(5));
mPrevWallpaperTarget = null;
@@ -570,8 +573,8 @@
// then we are in our super special mode!
boolean oldAnim = prevWallpaperTarget.isAnimatingLw();
boolean foundAnim = wallpaperTarget.isAnimatingLw();
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "New animation: " + foundAnim + " old animation: " + oldAnim);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "New animation: %s old animation: %s",
+ foundAnim, oldAnim);
if (!foundAnim || !oldAnim) {
return;
@@ -586,14 +589,14 @@
final boolean oldTargetHidden = prevWallpaperTarget.mActivityRecord != null
&& !prevWallpaperTarget.mActivityRecord.mVisibleRequested;
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Animating wallpapers:" + " old: "
- + prevWallpaperTarget + " hidden=" + oldTargetHidden + " new: " + wallpaperTarget
- + " hidden=" + newTargetHidden);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Animating wallpapers: "
+ + "old: %s hidden=%b new: %s hidden=%b",
+ prevWallpaperTarget, oldTargetHidden, wallpaperTarget, newTargetHidden);
mPrevWallpaperTarget = prevWallpaperTarget;
if (newTargetHidden && !oldTargetHidden) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Old wallpaper still the target.");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Old wallpaper still the target.");
// Use the old target if new target is hidden but old target
// is not. If they're both hidden, still use the new target.
mWallpaperTarget = prevWallpaperTarget;
@@ -661,8 +664,8 @@
/* x= */ 0, /* y= */ 0, /* z= */ 0, /* extras= */ null, /* sync= */ false);
}
- if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, "New wallpaper: target=" + mWallpaperTarget
- + " prev=" + mPrevWallpaperTarget);
+ ProtoLog.d(WM_DEBUG_WALLPAPER, "New wallpaper: target=%s prev=%s",
+ mWallpaperTarget, mPrevWallpaperTarget);
}
boolean processWallpaperDrawPendingTimeout() {
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 75c84c4..3a639f5 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -20,7 +20,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -28,7 +28,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Slog;
import android.view.DisplayInfo;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -107,8 +106,8 @@
void updateWallpaperWindows(boolean visible) {
if (isVisible() != visible) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG,
- "Wallpaper token " + token + " visible=" + visible);
+ ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
+ token, visible);
setVisibility(visible);
}
final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 9a4bf63..51ecce0 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -111,6 +111,7 @@
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -178,6 +179,10 @@
*/
protected final SurfaceAnimator mSurfaceAnimator;
+ /** The parent leash added for animation. */
+ @Nullable
+ private SurfaceControl mAnimationLeash;
+
final SurfaceFreezer mSurfaceFreezer;
protected final WindowManagerService mWmService;
final TransitionController mTransitionController;
@@ -2561,11 +2566,14 @@
* @param animationFinishedCallback The callback being triggered when the animation finishes.
* @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a
* cancel call to the underlying AnimationAdapter.
+ * @param snapshotAnim The animation to run for the snapshot. {@code null} if there is no
+ * snapshot.
*/
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
- @Nullable Runnable animationCancelledCallback) {
+ @Nullable Runnable animationCancelledCallback,
+ @Nullable AnimationAdapter snapshotAnim) {
if (DEBUG_ANIM) {
Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + anim);
}
@@ -2573,14 +2581,14 @@
// TODO: This should use isVisible() but because isVisible has a really weird meaning at
// the moment this doesn't work for all animatable window containers.
mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
- animationCancelledCallback, mSurfaceFreezer);
+ animationCancelledCallback, snapshotAnim, mSurfaceFreezer);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback) {
startAnimation(t, anim, hidden, type, animationFinishedCallback,
- null /* adapterAnimationCancelledCallback */);
+ null /* adapterAnimationCancelledCallback */, null /* snapshotAnim */);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@@ -2828,21 +2836,26 @@
taskDisplayArea.setBackgroundColor(backgroundColor);
}
+ // Atomic counter to make sure the clearColor callback is only called one.
+ // It will be called twice in the case we cancel the animation without restart
+ // (in that case it will run as the cancel and finished callbacks).
+ final AtomicInteger callbackCounter = new AtomicInteger(0);
+ final Runnable clearBackgroundColorHandler = () -> {
+ if (callbackCounter.getAndIncrement() == 0) {
+ taskDisplayArea.clearBackgroundColor();
+ }
+ };
+
final Runnable cleanUpCallback = isSettingBackgroundColor
- ? taskDisplayArea::clearBackgroundColor : () -> {};
+ ? clearBackgroundColorHandler : () -> {};
startAnimation(getPendingTransaction(), adapter, !isVisible(),
- ANIMATION_TYPE_APP_TRANSITION,
- (type, anim) -> cleanUpCallback.run(),
- cleanUpCallback);
+ ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> cleanUpCallback.run(),
+ cleanUpCallback, thumbnailAdapter);
if (adapter.getShowWallpaper()) {
getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
- if (thumbnailAdapter != null) {
- mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(),
- thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> { });
- }
}
}
@@ -2972,6 +2985,7 @@
@Override
public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
mLastLayer = -1;
+ mAnimationLeash = leash;
reassignLayer(t);
// Leash is now responsible for position, so set our position to 0.
@@ -2981,11 +2995,16 @@
@Override
public void onAnimationLeashLost(Transaction t) {
mLastLayer = -1;
- mSurfaceFreezer.unfreeze(t);
+ mAnimationLeash = null;
reassignLayer(t);
updateSurfacePosition(t);
}
+ @Override
+ public SurfaceControl getAnimationLeash() {
+ return mAnimationLeash;
+ }
+
private void doAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
for (int i = 0; i < mSurfaceAnimationSources.size(); ++i) {
mSurfaceAnimationSources.valueAt(i).onAnimationFinished(type, anim);
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 0840441..c954700 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -43,7 +43,6 @@
static final boolean DEBUG_CONFIGURATION = false;
static final boolean DEBUG_STARTING_WINDOW_VERBOSE = false;
static final boolean DEBUG_WALLPAPER = false;
- static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER;
static final boolean DEBUG_DRAG = true;
static final boolean DEBUG_SCREENSHOT = false;
static final boolean DEBUG_LAYOUT_REPEATS = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 776a65d..4cde048 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2472,7 +2472,8 @@
if (isPrimaryDisplay) {
transformHint = (transformHint + mPrimaryDisplayOrientation) % 4;
}
- outSurfaceControl.setTransformHint(transformHint);
+ outSurfaceControl.setTransformHint(
+ SurfaceControl.rotationToBufferTransform(transformHint));
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Passing transform hint %d for window %s%s",
transformHint, win,
@@ -6417,6 +6418,7 @@
pw.print(" mGlobalConfiguration="); pw.println(mRoot.getConfiguration());
pw.print(" mHasPermanentDpad="); pw.println(mHasPermanentDpad);
mRoot.dumpTopFocusedDisplayId(pw);
+ mRoot.dumpDefaultMinSizeOfResizableTask(pw);
mRoot.forAllDisplays(dc -> {
final int displayId = dc.getDisplayId();
final InsetsControlTarget imeLayeringTarget = dc.getImeTarget(IME_TARGET_LAYERING);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 54ce5fc..6288ab5 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -83,7 +83,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
+import java.util.function.Function;
/**
* Server side implementation for the interface for organizing windows
@@ -893,24 +893,31 @@
// We want to collect the tasks first before re-parenting to avoid array shifting on us.
final ArrayList<Task> tasksToReparent = new ArrayList<>();
- currentParent.forAllTasks((Consumer<Task>) (task) -> {
+ currentParent.forAllTasks((Function<Task, Boolean>) task -> {
Slog.i(TAG, " Processing task=" + task);
- if (task.mCreatedByOrganizer
- || task.getParent() != finalCurrentParent) {
+ final boolean reparent;
+ if (task.mCreatedByOrganizer || task.getParent() != finalCurrentParent) {
// We only care about non-organized task that are direct children of the thing we
// are reparenting from.
- return;
+ return false;
}
if (newParentInMultiWindow && !task.supportsMultiWindowInDisplayArea(newParentTda)) {
Slog.e(TAG, "reparentChildrenTasksHierarchyOp non-resizeable task to multi window,"
+ " task=" + task);
- return;
+ return false;
}
- if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())) return;
- if (!ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) return;
+ if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())
+ || !ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) {
+ return false;
+ }
- tasksToReparent.add(task);
- }, !hop.getToTop());
+ if (hop.getToTop()) {
+ tasksToReparent.add(0, task);
+ } else {
+ tasksToReparent.add(task);
+ }
+ return hop.getReparentTopOnly() && tasksToReparent.size() == 1;
+ });
final int count = tasksToReparent.size();
for (int i = 0; i < count; ++i) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 719d52c..af38641 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -952,7 +952,7 @@
final int displayId = r.getDisplayId();
final Context c = root.getDisplayUiContext(displayId);
- if (r.mVisibleRequested && !displayContexts.contains(c)) {
+ if (c != null && r.mVisibleRequested && !displayContexts.contains(c)) {
displayContexts.add(c);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 5a00e0d..62e0a19 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -998,6 +998,8 @@
throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
createSystemReadyService();
IExternalVibrationController firstController = mock(IExternalVibrationController.class);
@@ -1006,8 +1008,11 @@
firstController);
int firstScale = mExternalVibratorService.onExternalVibrationStart(firstVibration);
- ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS,
- secondController);
+ AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
+ ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ ringtoneAudioAttrs, secondController);
int secondScale = mExternalVibratorService.onExternalVibrationStart(secondVibration);
assertEquals(IExternalVibratorService.SCALE_NONE, firstScale);
@@ -1040,6 +1045,37 @@
assertEquals(Arrays.asList(true), mVibratorProviders.get(1).getExternalControlStates());
}
+ @Test
+ public void onExternalVibration_withRingtone_usesRingerModeSettings() {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
+ AudioAttributes audioAttrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
+ mock(IExternalVibrationController.class));
+
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ createSystemReadyService();
+ int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
+ createSystemReadyService();
+ scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertEquals(IExternalVibratorService.SCALE_NONE, scale);
+
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ createSystemReadyService();
+ scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertEquals(IExternalVibratorService.SCALE_NONE, scale);
+ }
+
private VibrationEffectSegment expectedPrebaked(int effectId) {
return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 82140f4..5d0e34a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -43,6 +43,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -893,6 +894,33 @@
}
@Test
+ public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new TestRemoteAnimationRunner(), 10, 1);
+ setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(
+ createTask(mDisplayContent), organizer);
+ final ActivityRecord activity = taskFragment.getTopMostActivity();
+ activity.allDrawn = true;
+ // Set wallpaper as visible.
+ final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+ mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+ spyOn(mDisplayContent.mWallpaperController);
+ doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare a transition.
+ prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
+
+ // Should not be overridden when there is wallpaper in the transition.
+ verify(mDisplayContent.mAppTransition, never())
+ .overridePendingAppTransitionRemote(adapter, false /* sync */);
+ }
+
+ @Test
public void testTransitionGoodToGoForTaskFragments() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final Task task = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index e528a4a..168c250 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -1321,6 +1321,50 @@
}
@Test
+ public void testDefaultFreeformSizeRespectsMinAspectRatio() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+ mActivity.info.setMinAspectRatio(5f);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder()
+ .setOptions(options).calculate());
+
+ final float aspectRatio =
+ (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height())
+ / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height());
+ assertTrue("Bounds aspect ratio should be at least 5.0, but was " + aspectRatio,
+ aspectRatio >= 5f);
+ }
+
+ @Test
+ public void testDefaultFreeformSizeRespectsMaxAspectRatio() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+ mActivity.info.setMaxAspectRatio(1.5f);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder()
+ .setOptions(options).calculate());
+
+ final float aspectRatio =
+ (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height())
+ / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height());
+ assertTrue("Bounds aspect ratio should be at most 1.5, but was " + aspectRatio,
+ aspectRatio <= 1.5f);
+ }
+
+ @Test
public void testCascadesToSourceSizeForFreeform() {
final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
@@ -1348,6 +1392,72 @@
}
@Test
+ public void testCascadesToSourceSizeForFreeformRespectingMinAspectRatio() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ final ActivityRecord source = createSourceActivity(freeformDisplay);
+ source.setBounds(0, 0, 412, 732);
+
+ mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+ mActivity.info.setMinAspectRatio(5f);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setSource(source).setOptions(options).calculate());
+
+ final Rect displayBounds = freeformDisplay.getBounds();
+ assertTrue("Left bounds should be larger than 0.", mResult.mBounds.left > 0);
+ assertTrue("Top bounds should be larger than 0.", mResult.mBounds.top > 0);
+ assertTrue("Bounds should be centered at somewhere in the left half, but it's "
+ + "centerX is " + mResult.mBounds.centerX(),
+ mResult.mBounds.centerX() < displayBounds.centerX());
+ assertTrue("Bounds should be centered at somewhere in the top half, but it's "
+ + "centerY is " + mResult.mBounds.centerY(),
+ mResult.mBounds.centerY() < displayBounds.centerY());
+ final float aspectRatio =
+ (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height())
+ / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height());
+ assertTrue("Bounds aspect ratio should be at least 5.0, but was " + aspectRatio,
+ aspectRatio >= 5f);
+ }
+
+ @Test
+ public void testCascadesToSourceSizeForFreeformRespectingMaxAspectRatio() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ final ActivityRecord source = createSourceActivity(freeformDisplay);
+ source.setBounds(0, 0, 412, 732);
+
+ mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+ mActivity.info.setMaxAspectRatio(1.5f);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setSource(source).setOptions(options).calculate());
+
+ final Rect displayBounds = freeformDisplay.getBounds();
+ assertTrue("Left bounds should be larger than 0.", mResult.mBounds.left > 0);
+ assertTrue("Top bounds should be larger than 0.", mResult.mBounds.top > 0);
+ assertTrue("Bounds should be centered at somewhere in the left half, but it's "
+ + "centerX is " + mResult.mBounds.centerX(),
+ mResult.mBounds.centerX() < displayBounds.centerX());
+ assertTrue("Bounds should be centered at somewhere in the top half, but it's "
+ + "centerY is " + mResult.mBounds.centerY(),
+ mResult.mBounds.centerY() < displayBounds.centerY());
+ final float aspectRatio =
+ (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height())
+ / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height());
+ assertTrue("Bounds aspect ratio should be at most 1.5, but was " + aspectRatio,
+ aspectRatio <= 1.5f);
+ }
+
+ @Test
public void testAdjustBoundsToFitDisplay_TopLeftOutOfDisplay() {
final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 68053eb..bbeb980 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -25,6 +25,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
@@ -52,6 +53,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -1106,6 +1108,71 @@
verify(surfaceAnimator, never()).setRelativeLayer(any(), any(), anyInt());
}
+ @Test
+ public void testStartChangeTransitionWhenPreviousIsNotFinished() {
+ final WindowContainer container = createTaskFragmentWithParentTask(
+ createTask(mDisplayContent), false);
+ container.mSurfaceControl = mock(SurfaceControl.class);
+ final SurfaceAnimator surfaceAnimator = container.mSurfaceAnimator;
+ final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer;
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ spyOn(container);
+ spyOn(surfaceAnimator);
+ spyOn(surfaceFreezer);
+ doReturn(t).when(container).getPendingTransaction();
+ doReturn(t).when(container).getSyncTransaction();
+
+ // Leash and snapshot created for change transition.
+ container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
+ // Can't really take a snapshot, manually set one.
+ surfaceFreezer.mSnapshot = mock(SurfaceFreezer.Snapshot.class);
+
+ assertNotNull(surfaceFreezer.mLeash);
+ assertEquals(surfaceFreezer.mLeash, container.getAnimationLeash());
+
+ // Start animation: surfaceAnimator take over the leash and snapshot from surfaceFreezer.
+ container.applyAnimationUnchecked(null /* lp */, true /* enter */,
+ TRANSIT_OLD_TASK_FRAGMENT_CHANGE, false /* isVoiceInteraction */,
+ null /* sources */);
+
+ assertNull(surfaceFreezer.mLeash);
+ assertNull(surfaceFreezer.mSnapshot);
+ assertNotNull(surfaceAnimator.mLeash);
+ assertNotNull(surfaceAnimator.mSnapshot);
+ final SurfaceControl prevLeash = surfaceAnimator.mLeash;
+ final SurfaceFreezer.Snapshot prevSnapshot = surfaceAnimator.mSnapshot;
+
+ // Prepare another change transition.
+ container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
+ surfaceFreezer.mSnapshot = mock(SurfaceFreezer.Snapshot.class);
+
+ assertNotNull(surfaceFreezer.mLeash);
+ assertEquals(surfaceFreezer.mLeash, container.getAnimationLeash());
+ assertNotEquals(prevLeash, container.getAnimationLeash());
+
+ // Start another animation before the previous one is finished, it should reset the previous
+ // one, but not change the current one.
+ container.applyAnimationUnchecked(null /* lp */, true /* enter */,
+ TRANSIT_OLD_TASK_FRAGMENT_CHANGE, false /* isVoiceInteraction */,
+ null /* sources */);
+
+ verify(container, never()).onAnimationLeashLost(any());
+ verify(surfaceFreezer, never()).unfreeze(any());
+ assertNotNull(surfaceAnimator.mLeash);
+ assertNotNull(surfaceAnimator.mSnapshot);
+ assertEquals(surfaceAnimator.mLeash, container.getAnimationLeash());
+ assertNotEquals(prevLeash, surfaceAnimator.mLeash);
+ assertNotEquals(prevSnapshot, surfaceAnimator.mSnapshot);
+
+ // Clean up after animation finished.
+ surfaceAnimator.mInnerAnimationFinishedCallback.onAnimationFinished(
+ ANIMATION_TYPE_APP_TRANSITION, surfaceAnimator.getAnimation());
+
+ verify(container).onAnimationLeashLost(any());
+ assertNull(surfaceAnimator.mLeash);
+ assertNull(surfaceAnimator.mSnapshot);
+ }
+
/* Used so we can gain access to some protected members of the {@link WindowContainer} class */
private static class TestWindowContainer extends WindowContainer<TestWindowContainer> {
private final int mLayer;
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 562a0bd..64cb790 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -102,35 +102,33 @@
}
}
-@JvmOverloads
-fun FlickerTestParameter.navBarLayerRotatesAndScales(
- beginRotation: Int,
- endRotation: Int = beginRotation
-) {
- val startingPos = WindowUtils.getNavigationBarPosition(beginRotation)
- val endingPos = WindowUtils.getNavigationBarPosition(endRotation)
-
+fun FlickerTestParameter.navBarLayerRotatesAndScales() {
assertLayersStart {
- this.visibleRegion(FlickerComponentName.NAV_BAR).coversExactly(startingPos)
+ val display = this.entry.displays.minByOrNull { it.id }
+ ?: throw RuntimeException("There is no display!")
+ this.visibleRegion(FlickerComponentName.NAV_BAR)
+ .coversExactly(WindowUtils.getNavigationBarPosition(display))
}
assertLayersEnd {
- this.visibleRegion(FlickerComponentName.NAV_BAR).coversExactly(endingPos)
+ val display = this.entry.displays.minByOrNull { it.id }
+ ?: throw RuntimeException("There is no display!")
+ this.visibleRegion(FlickerComponentName.NAV_BAR)
+ .coversExactly(WindowUtils.getNavigationBarPosition(display))
}
}
-@JvmOverloads
-fun FlickerTestParameter.statusBarLayerRotatesScales(
- beginRotation: Int,
- endRotation: Int = beginRotation
-) {
- val startingPos = WindowUtils.getStatusBarPosition(beginRotation)
- val endingPos = WindowUtils.getStatusBarPosition(endRotation)
-
+fun FlickerTestParameter.statusBarLayerRotatesScales() {
assertLayersStart {
- this.visibleRegion(FlickerComponentName.STATUS_BAR).coversExactly(startingPos)
+ val display = this.entry.displays.minByOrNull { it.id }
+ ?: throw RuntimeException("There is no display!")
+ this.visibleRegion(FlickerComponentName.STATUS_BAR)
+ .coversExactly(WindowUtils.getStatusBarPosition(display))
}
assertLayersEnd {
- this.visibleRegion(FlickerComponentName.STATUS_BAR).coversExactly(endingPos)
+ val display = this.entry.displays.minByOrNull { it.id }
+ ?: throw RuntimeException("There is no display!")
+ this.visibleRegion(FlickerComponentName.STATUS_BAR)
+ .coversExactly(WindowUtils.getStatusBarPosition(display))
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 9b34853..9f26c31 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -17,7 +17,6 @@
package com.android.server.wm.flicker.close
-import android.platform.test.annotations.Postsubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -80,11 +79,6 @@
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
companion object {
/**
* Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index e380794..795766f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.close
-import android.platform.test.annotations.Postsubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -79,11 +78,6 @@
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
companion object {
/**
* Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index 0482619..511fc26 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -18,7 +18,6 @@
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
@@ -114,18 +113,14 @@
*/
@Presubmit
@Test
- open fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
+ open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
/**
* Checks the position of the status bar at the start and end of the transition
*/
@Presubmit
@Test
- open fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
+ open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
/**
* Checks that all windows that are visible on the trace, are visible for at least 2
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
new file mode 100644
index 0000000..be68704
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.flicker.helpers
+
+import android.app.Instrumentation
+import android.support.test.launcherhelper.ILauncherStrategy
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+
+class NewTasksAppHelper @JvmOverloads constructor(
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME,
+ component: FlickerComponentName =
+ ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+ .getInstance(instr)
+ .launcherStrategy
+) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ fun openNewTask(device: UiDevice, wmHelper: WindowManagerStateHelper) {
+ val button = device.wait(
+ Until.findObject(By.res(getPackage(), "launch_new_task")),
+ FIND_TIMEOUT)
+
+ require(button != null) {
+ "Button not found, this usually happens when the device " +
+ "was left in an unknown state (e.g. in split screen)"
+ }
+ button.click()
+ wmHelper.waitForAppTransitionIdle()
+ wmHelper.waitForFullScreenApp(component)
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 3550536..5e21aff 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -151,15 +151,11 @@
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
- }
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation)
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index f7f325e..0582685 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -150,15 +150,11 @@
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index 11660df..91b3d3d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -32,7 +32,6 @@
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
@@ -121,21 +120,19 @@
@Test
fun navBarLayerRotatesAndScales() {
Assume.assumeFalse(testSpec.isRotated)
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
+ testSpec.navBarLayerRotatesAndScales()
}
@FlakyTest
@Test
fun navBarLayerRotatesAndScales_Flaky() {
Assume.assumeTrue(testSpec.isRotated)
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
+ testSpec.navBarLayerRotatesAndScales()
}
@Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation)
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index bb2ffbc..b589969 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -34,7 +34,6 @@
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
@@ -143,14 +142,11 @@
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
new file mode 100644
index 0000000..a9568b3
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.flicker.ime
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Launch an app that automatically displays the IME
+ *
+ * To run this test: `atest FlickerTests:LaunchAppShowImeOnStartTest`
+ *
+ * Actions:
+ * Make sure no apps are running on the device
+ * Launch an app [testApp] that automatically displays IME and wait animation to complete
+ *
+ * To run only the presubmit assertions add: `--
+ * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
+ * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
+ *
+ * To run only the postsubmit assertions add: `--
+ * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
+ * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
+ *
+ * To run only the flaky assertions add: `--
+ * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [CloseAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation)
+
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ setup {
+ eachRun {
+ this.setRotation(testSpec.config.startRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ testApp.exit()
+ }
+ }
+ transitions {
+ testApp.launchViaIntent(wmHelper)
+ wmHelper.waitImeShown()
+ }
+ }
+ }
+
+ /**
+ * Checks that [FlickerComponentName.IME] window becomes visible during the transition
+ */
+ @Presubmit
+ @Test
+ fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
+
+ /**
+ * Checks that [FlickerComponentName.IME] layer becomes visible during the transition
+ */
+ @Presubmit
+ @Test
+ fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+
+ /**
+ * Checks that [FlickerComponentName.IME] layer is invisible at the start of the transition
+ */
+ @Presubmit
+ @Test
+ fun imeLayerNotExistsStart() {
+ testSpec.assertLayersStart {
+ this.isInvisible(FlickerComponentName.IME)
+ }
+ }
+
+ /**
+ * Checks that [FlickerComponentName.IME] layer is visible at the end of the transition
+ */
+ @Presubmit
+ @Test
+ fun imeLayerExistsEnd() {
+ testSpec.assertLayersEnd {
+ this.isVisible(FlickerComponentName.IME)
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(
+ repetitions = 5,
+ supportedRotations = listOf(Surface.ROTATION_0),
+ supportedNavigationModes = listOf(
+ WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+ )
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index 44a27b1..7bf0186 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -34,7 +34,6 @@
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
@@ -125,15 +124,11 @@
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
- }
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation)
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index 7a01703..f6febe9e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -38,7 +38,6 @@
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
@@ -207,15 +206,11 @@
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation)
- }
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation)
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 3678f33..663af70 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -27,6 +27,7 @@
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -78,6 +79,11 @@
}
transitions {
device.reopenAppFromOverview(wmHelper)
+ wmHelper.waitFor(
+ WindowManagerConditionsFactory.hasLayersAnimating().negate(),
+ WindowManagerConditionsFactory.isWMStateComplete(),
+ WindowManagerConditionsFactory.isHomeActivityVisible().negate()
+ )
wmHelper.waitForFullScreenApp(testApp.component)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 1bdc235..cf10c53 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -28,6 +28,7 @@
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import com.google.common.truth.Truth
import org.junit.FixMethodOrder
@@ -107,7 +108,7 @@
* Checks that the app layer doesn't exist at the start of the transition, that it is
* created (invisible) and becomes visible during the transition
*/
- @Presubmit
+ @FlakyTest
@Test
fun appLayerBecomesVisible() {
testSpec.assertLayers {
@@ -168,6 +169,11 @@
/** {@inheritDoc} */
@FlakyTest
@Test
+ override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
+
+ /** {@inheritDoc} */
+ @FlakyTest
+ @Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 419d3e8..7af7b3a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -18,13 +18,11 @@
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.StandardAppHelper
@@ -98,9 +96,7 @@
*/
@Presubmit
@Test
- open fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation)
- }
+ open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
/**
* Checks that the status bar window is visible during the whole transition
@@ -125,9 +121,7 @@
*/
@Presubmit
@Test
- open fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation)
- }
+ open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
/**
* Checks that all windows that are visible on the trace, are visible for at least 2
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
new file mode 100644
index 0000000..495e2d6
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -0,0 +1,248 @@
+/*
+ * 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.flicker.launch
+
+import android.app.Instrumentation
+import android.app.WallpaperManager
+import android.platform.test.annotations.Postsubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.navBarLayerIsVisible
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerIsVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.flicker.testapp.ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME
+import com.android.server.wm.flicker.testapp.ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.FlickerComponentName.Companion.SPLASH_SCREEN
+import com.android.server.wm.traces.common.FlickerComponentName.Companion.WALLPAPER_BBQ_WRAPPER
+import com.android.server.wm.traces.parser.toFlickerComponent
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test the back and forward transition between 2 activities.
+ *
+ * To run this test: `atest FlickerTests:ActivitiesTransitionTest`
+ *
+ * Actions:
+ * Launch the NewTaskLauncherApp [mTestApp]
+ * Open a new task (SimpleActivity) from the NewTaskLauncherApp [mTestApp]
+ * Go back to the NewTaskLauncherApp [mTestApp]
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group4
+class TaskTransitionTest(val testSpec: FlickerTestParameter) {
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val mTestApp: NewTasksAppHelper = NewTasksAppHelper(instrumentation)
+
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ withTestName { testSpec.name }
+ repeat { testSpec.config.repetitions }
+ setup {
+ eachRun {
+ mTestApp.launchViaIntent(wmHelper)
+ wmHelper.waitForFullScreenApp(mTestApp.component)
+ }
+ }
+ teardown {
+ test {
+ mTestApp.exit()
+ }
+ }
+ transitions {
+ mTestApp.openNewTask(device, wmHelper)
+ device.pressBack()
+ wmHelper.waitForAppTransitionIdle()
+ wmHelper.waitForFullScreenApp(mTestApp.component)
+ }
+ }
+ }
+
+ /**
+ * Checks that the wallpaper window is never visible when performing task transitions.
+ * A solid color background should be shown instead.
+ */
+ @Postsubmit
+ @Test
+ fun wallpaperWindowIsNeverVisible() {
+ testSpec.assertWm {
+ this.isNonAppWindowInvisible(WALLPAPER)
+ }
+ }
+
+ /**
+ * Checks that the wallpaper layer is never visible when performing task transitions.
+ * A solid color background should be shown instead.
+ */
+ @Postsubmit
+ @Test
+ fun wallpaperLayerIsNeverVisible() {
+ testSpec.assertLayers {
+ this.isInvisible(WALLPAPER)
+ this.isInvisible(WALLPAPER_BBQ_WRAPPER)
+ }
+ }
+
+ /**
+ * Check that the launcher window is never visible when performing task transitions.
+ * A solid color background should be shown above it.
+ */
+ @Postsubmit
+ @Test
+ fun launcherWindowIsNeverVisible() {
+ testSpec.assertWm {
+ this.isAppWindowInvisible(LAUNCHER_COMPONENT)
+ }
+ }
+
+ /**
+ * Checks that the launcher layer is never visible when performing task transitions.
+ * A solid color background should be shown above it.
+ */
+ @Postsubmit
+ @Test
+ fun launcherLayerIsNeverVisible() {
+ testSpec.assertLayers {
+ this.isInvisible(LAUNCHER_COMPONENT)
+ }
+ }
+
+ /**
+ * Checks that a color background is visible while the task transition is occurring.
+ */
+ @Postsubmit
+ @Test
+ fun colorLayerIsVisibleDuringTransition() {
+ val bgColorLayer = FlickerComponentName("", "colorBackgroundLayer")
+ val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+
+ testSpec.assertLayers {
+ this.coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY)
+ .isInvisible(bgColorLayer)
+ .then()
+ // Transitioning
+ .isVisible(bgColorLayer)
+ .then()
+ // Fully transitioned to simple SIMPLE_ACTIVITY
+ .coversExactly(displayBounds, SIMPLE_ACTIVITY)
+ .isInvisible(bgColorLayer)
+ .then()
+ // Transitioning back
+ .isVisible(bgColorLayer)
+ .then()
+ // Fully transitioned back to LAUNCH_NEW_TASK_ACTIVITY
+ .isInvisible(bgColorLayer)
+ .coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY)
+ }
+ }
+
+ /**
+ * Checks that we start with the LaunchNewTask activity on top and then open up
+ * the SimpleActivity and then go back to the LaunchNewTask activity.
+ */
+ @Postsubmit
+ @Test
+ fun newTaskOpensOnTopAndThenCloses() {
+ testSpec.assertWm {
+ this.isAppWindowOnTop(LAUNCH_NEW_TASK_ACTIVITY)
+ .then()
+ .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true)
+ .then()
+ .isAppWindowOnTop(SIMPLE_ACTIVITY)
+ .then()
+ .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true)
+ .then()
+ .isAppWindowOnTop(LAUNCH_NEW_TASK_ACTIVITY)
+ }
+ }
+
+ /**
+ * Checks that all parts of the screen are covered at the start and end of the transition
+ */
+ @Postsubmit
+ @Test
+ fun entireScreenCovered() = testSpec.entireScreenCovered()
+
+ /**
+ * Checks that the navbar window is visible throughout the transition
+ */
+ @Postsubmit
+ @Test
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
+
+ /**
+ * Checks that the navbar layer is visible throughout the transition
+ */
+ @Postsubmit
+ @Test
+ fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
+
+ /**
+ * Checks that the status bar window is visible throughout the transition
+ */
+ @Postsubmit
+ @Test
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
+
+ /**
+ * Checks that the status bar layer is visible throughout the transition
+ */
+ @Postsubmit
+ @Test
+ fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
+
+ companion object {
+ private val WALLPAPER = getWallpaperPackage(InstrumentationRegistry.getInstrumentation())
+ private val LAUNCH_NEW_TASK_ACTIVITY =
+ LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
+ private val SIMPLE_ACTIVITY = SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+
+ private fun getWallpaperPackage(instrumentation: Instrumentation): FlickerComponentName {
+ val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext)
+
+ return wallpaperManager.wallpaperInfo.component.toFlickerComponent()
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(repetitions = 5)
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index cdab681..52904cc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -297,8 +297,7 @@
*/
@Postsubmit
@Test
- fun navbarIsAlwaysInRightPosition() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
+ fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales()
/**
* Checks that the status bar window is visible throughout the entire transition.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index d1a3fe4..842aa2b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -314,8 +314,7 @@
*/
@Postsubmit
@Test
- fun navbarIsAlwaysInRightPosition() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
+ fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales()
/**
* Checks that the status bar window is visible throughout the entire transition.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 0389f7c..10ca0d9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -305,8 +305,7 @@
*/
@Presubmit
@Test
- fun navbarIsAlwaysInRightPosition() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
+ fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales()
/**
* Checks that the status bar window is visible throughout the entire transition.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 878821a..fd8abc6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.rotation
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -24,14 +25,14 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.FixMethodOrder
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -80,6 +81,9 @@
class ChangeAppRotationTest(
testSpec: FlickerTestParameter
) : RotationTransition(testSpec) {
+ @get:Rule
+ val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec)
+
override val testApp = SimpleAppHelper(instrumentation)
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = {
@@ -91,6 +95,24 @@
}
}
+ @Postsubmit
+ @Test
+ fun runPresubmitAssertion() {
+ flickerRule.checkPresubmitAssertions()
+ }
+
+ @Postsubmit
+ @Test
+ fun runPostsubmitAssertion() {
+ flickerRule.checkPostsubmitAssertions()
+ }
+
+ @FlakyTest
+ @Test
+ fun runFlakyAssertion() {
+ flickerRule.checkFlakyAssertions()
+ }
+
/** {@inheritDoc} */
@FlakyTest(bugId = 190185577)
@Test
@@ -111,6 +133,7 @@
.isVisible(FlickerComponentName.ROTATION)
.then()
.isVisible(testApp.component)
+ .isInvisible(FlickerComponentName.ROTATION)
}
}
@@ -138,10 +161,7 @@
*/
@Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(
- testSpec.config.startRotation, testSpec.config.endRotation)
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
/** {@inheritDoc} */
@FlakyTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 2b03396..e850632 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -92,10 +92,7 @@
*/
@Presubmit
@Test
- open fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(
- testSpec.config.startRotation, testSpec.config.endRotation)
- }
+ open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
/**
* Checks that all layers that are visible on the trace, are visible for at least 2
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 3b9f33a..cb37fc7 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -80,5 +80,15 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".LaunchNewTaskActivity"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewTaskActivity"
+ android:configChanges="orientation|screenSize"
+ android:label="LaunchNewTaskActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml
new file mode 100644
index 0000000..8f75d17
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2018 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/holo_orange_light">
+ <Button
+ android:id="@+id/launch_new_task"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="New task" />
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 224d2ac..baf36ab 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -51,4 +51,9 @@
public static final ComponentName BUTTON_ACTIVITY_COMPONENT_NAME =
new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".ButtonActivity");
+
+ public static final String LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME = "LaunchNewTaskApp";
+ public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME =
+ new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity");
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java
new file mode 100644
index 0000000..1809781
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 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.flicker.testapp;
+
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+import android.widget.Button;
+
+public class LaunchNewTaskActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ WindowManager.LayoutParams p = getWindow().getAttributes();
+ p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
+ .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ getWindow().setAttributes(p);
+ setContentView(R.layout.task_button);
+
+ Button button = findViewById(R.id.launch_new_task);
+ button.setOnClickListener(v -> {
+ Intent intent = new Intent(LaunchNewTaskActivity.this, SimpleActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+ startActivity(intent);
+ });
+ }
+}