Merge "Split CUJ_IME_INSETS_ANIMATION by animation type" into udc-qpr-dev
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 4e2b6fa..d189bab 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -24,7 +24,6 @@
import android.app.IApplicationThread;
import android.app.IActivityClientController;
import android.app.IActivityController;
-import android.app.IAppTask;
import android.app.IAssistDataReceiver;
import android.app.IInstrumentationWatcher;
import android.app.IProcessObserver;
@@ -107,14 +106,6 @@
in ProfilerInfo profilerInfo, in Bundle options, int userId);
boolean startNextMatchingActivity(in IBinder callingActivity,
in Intent intent, in Bundle options);
-
- /**
- * The DreamActivity has to be started in a special way that does not involve the PackageParser.
- * The DreamActivity is a framework component inserted in the dream application process. Hence,
- * it is not declared in the application's manifest and cannot be parsed. startDreamActivity
- * creates the activity and starts it without reaching out to the PackageParser.
- */
- boolean startDreamActivity(in Intent intent);
int startActivityIntentSender(in IApplicationThread caller,
in IIntentSender target, in IBinder whitelistToken, in Intent fillInIntent,
in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8d2394b..44068dd 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2171,6 +2171,10 @@
}
}
+ private void visitUris(@NonNull Consumer<Uri> visitor) {
+ visitIconUri(visitor, getIcon());
+ }
+
@Override
public Action clone() {
return new Action(
@@ -2858,7 +2862,7 @@
if (actions != null) {
for (Action action : actions) {
- visitIconUri(visitor, action.getIcon());
+ action.visitUris(visitor);
}
}
@@ -2939,6 +2943,11 @@
if (mBubbleMetadata != null) {
visitIconUri(visitor, mBubbleMetadata.getIcon());
}
+
+ if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) {
+ WearableExtender extender = new WearableExtender(this);
+ extender.visitUris(visitor);
+ }
}
/**
@@ -3038,10 +3047,9 @@
// cannot look into the extras as there may be parcelables there that
// the platform does not know how to handle. To go around that we have
// an explicit list of the pending intents in the extras bundle.
- final boolean collectPendingIntents = (allPendingIntents == null);
- if (collectPendingIntents) {
- PendingIntent.setOnMarshaledListener(
- (PendingIntent intent, Parcel out, int outFlags) -> {
+ PendingIntent.OnMarshaledListener addedListener = null;
+ if (allPendingIntents == null) {
+ addedListener = (PendingIntent intent, Parcel out, int outFlags) -> {
if (parcel == out) {
synchronized (this) {
if (allPendingIntents == null) {
@@ -3050,7 +3058,8 @@
allPendingIntents.add(intent);
}
}
- });
+ };
+ PendingIntent.addOnMarshaledListener(addedListener);
}
try {
// IMPORTANT: Add marshaling code in writeToParcelImpl as we
@@ -3061,8 +3070,8 @@
parcel.writeArraySet(allPendingIntents);
}
} finally {
- if (collectPendingIntents) {
- PendingIntent.setOnMarshaledListener(null);
+ if (addedListener != null) {
+ PendingIntent.removeOnMarshaledListener(addedListener);
}
}
}
@@ -3468,8 +3477,11 @@
*
* @hide
*/
- public void setAllowlistToken(@Nullable IBinder token) {
- mAllowlistToken = token;
+ public void clearAllowlistToken() {
+ mAllowlistToken = null;
+ if (publicVersion != null) {
+ publicVersion.clearAllowlistToken();
+ }
}
/**
@@ -11715,6 +11727,12 @@
mFlags &= ~mask;
}
}
+
+ private void visitUris(@NonNull Consumer<Uri> visitor) {
+ for (Action action : mActions) {
+ action.visitUris(visitor);
+ }
+ }
}
/**
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 705b5ee..c7522b4 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -64,6 +64,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -391,11 +392,12 @@
void onMarshaled(PendingIntent intent, Parcel parcel, int flags);
}
- private static final ThreadLocal<OnMarshaledListener> sOnMarshaledListener
- = new ThreadLocal<>();
+ private static final ThreadLocal<List<OnMarshaledListener>> sOnMarshaledListener =
+ ThreadLocal.withInitial(ArrayList::new);
/**
- * Registers an listener for pending intents being written to a parcel.
+ * Registers an listener for pending intents being written to a parcel. This replaces any
+ * listeners previously added.
*
* @param listener The listener, null to clear.
*
@@ -403,7 +405,27 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static void setOnMarshaledListener(OnMarshaledListener listener) {
- sOnMarshaledListener.set(listener);
+ final List<OnMarshaledListener> listeners = sOnMarshaledListener.get();
+ listeners.clear();
+ if (listener != null) {
+ listeners.add(listener);
+ }
+ }
+
+ /**
+ * Adds a listener for pending intents being written to a parcel.
+ * @hide
+ */
+ static void addOnMarshaledListener(OnMarshaledListener listener) {
+ sOnMarshaledListener.get().add(listener);
+ }
+
+ /**
+ * Removes a listener for pending intents being written to a parcel.
+ * @hide
+ */
+ static void removeOnMarshaledListener(OnMarshaledListener listener) {
+ sOnMarshaledListener.get().remove(listener);
}
private static void checkPendingIntent(int flags, @NonNull Intent intent,
@@ -1451,11 +1473,11 @@
public void writeToParcel(Parcel out, int flags) {
out.writeStrongBinder(mTarget.asBinder());
- OnMarshaledListener listener = sOnMarshaledListener.get();
- if (listener != null) {
- listener.onMarshaled(this, out, flags);
+ final List<OnMarshaledListener> listeners = sOnMarshaledListener.get();
+ final int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onMarshaled(this, out, flags);
}
-
}
public static final @NonNull Creator<PendingIntent> CREATOR = new Creator<PendingIntent>() {
@@ -1483,9 +1505,10 @@
@NonNull Parcel out) {
out.writeStrongBinder(sender != null ? sender.mTarget.asBinder() : null);
if (sender != null) {
- OnMarshaledListener listener = sOnMarshaledListener.get();
- if (listener != null) {
- listener.onMarshaled(sender, out, 0 /* flags */);
+ final List<OnMarshaledListener> listeners = sOnMarshaledListener.get();
+ final int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onMarshaled(sender, out, 0 /* flags */);
}
}
}
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 3d76b28..d7195a7 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -65,7 +65,7 @@
* thread of your app.
*
* <p>Note on threading: the state inside of this class is not itself
- * thread-safe, however you can use it from any thread if you properly
+ * thread-safe. However, you can use it from any thread if you make
* sure that you do not have races. Typically this means you will hand
* the entire object to another thread, which will be solely responsible
* for setting any results and finally calling {@link #finish()}.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 47a5db8..3fdd023 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -770,6 +770,11 @@
void setSplashScreenTheme(String packageName, String themeName, int userId);
+ int getUserMinAspectRatio(String packageName, int userId);
+
+ @EnforcePermission("INSTALL_PACKAGES")
+ void setUserMinAspectRatio(String packageName, int userId, int aspectRatio);
+
List<String> getMimeGroup(String packageName, String group);
boolean isAutoRevokeWhitelisted(String packageName);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 105b38a..4b883cd 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -3045,10 +3045,6 @@
* The update ownership enforcement can only be enabled on initial installation. Set
* this to {@code true} on package update is a no-op.
*
- * Apps may opt themselves out of update ownership by setting the
- * <a href="https://developer.android.com/guide/topics/manifest/manifest-element.html#allowupdateownership">android:alllowUpdateOwnership</a>
- * attribute in their manifest to <code>false</code>.
- *
* Note: To enable the update ownership enforcement, the installer must have the
* {@link android.Manifest.permission#ENFORCE_UPDATE_OWNERSHIP ENFORCE_UPDATE_OWNERSHIP}
* permission.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8fafb18..66aadac 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2342,6 +2342,64 @@
*/
public static final int INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST = -130;
+ /**
+ * App minimum aspect ratio set by the user which will override app-defined aspect ratio.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "USER_MIN_ASPECT_RATIO_" }, value = {
+ USER_MIN_ASPECT_RATIO_UNSET,
+ USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
+ USER_MIN_ASPECT_RATIO_DISPLAY_SIZE,
+ USER_MIN_ASPECT_RATIO_4_3,
+ USER_MIN_ASPECT_RATIO_16_9,
+ USER_MIN_ASPECT_RATIO_3_2,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserMinAspectRatio {}
+
+ /**
+ * No aspect ratio override has been set by user.
+ *
+ * @hide
+ */
+ public static final int USER_MIN_ASPECT_RATIO_UNSET = 0;
+
+ /**
+ * Aspect ratio override code: user forces app to split screen aspect ratio. This is adjusted to
+ * half of the screen without the split screen divider.
+ *
+ * @hide
+ */
+ public static final int USER_MIN_ASPECT_RATIO_SPLIT_SCREEN = 1;
+
+ /**
+ * Aspect ratio override code: user forces app to the aspect ratio of the device display size.
+ * This will be the portrait aspect ratio of the device if the app is portrait or the landscape
+ * aspect ratio of the device if the app is landscape.
+ *
+ * @hide
+ */
+ public static final int USER_MIN_ASPECT_RATIO_DISPLAY_SIZE = 2;
+
+ /**
+ * Aspect ratio override code: user forces app to 4:3 min aspect ratio
+ * @hide
+ */
+ public static final int USER_MIN_ASPECT_RATIO_4_3 = 3;
+
+ /**
+ * Aspect ratio override code: user forces app to 16:9 min aspect ratio
+ * @hide
+ */
+ public static final int USER_MIN_ASPECT_RATIO_16_9 = 4;
+
+ /**
+ * Aspect ratio override code: user forces app to 3:2 min aspect ratio
+ * @hide
+ */
+ public static final int USER_MIN_ASPECT_RATIO_3_2 = 5;
+
/** @hide */
@IntDef(flag = true, prefix = { "DELETE_" }, value = {
DELETE_KEEP_DATA,
diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java
index a7cd168..690dfcf 100644
--- a/core/java/android/content/res/ThemedResourceCache.java
+++ b/core/java/android/content/res/ThemedResourceCache.java
@@ -137,8 +137,10 @@
*/
@UnsupportedAppUsage
public void onConfigurationChange(@Config int configChanges) {
- prune(configChanges);
- mGeneration++;
+ synchronized (this) {
+ pruneLocked(configChanges);
+ mGeneration++;
+ }
}
/**
@@ -214,22 +216,20 @@
* simply prune missing weak references
* @return {@code true} if the cache is completely empty after pruning
*/
- private boolean prune(@Config int configChanges) {
- synchronized (this) {
- if (mThemedEntries != null) {
- for (int i = mThemedEntries.size() - 1; i >= 0; i--) {
- if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) {
- mThemedEntries.removeAt(i);
- }
+ private boolean pruneLocked(@Config int configChanges) {
+ if (mThemedEntries != null) {
+ for (int i = mThemedEntries.size() - 1; i >= 0; i--) {
+ if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) {
+ mThemedEntries.removeAt(i);
}
}
-
- pruneEntriesLocked(mNullThemedEntries, configChanges);
- pruneEntriesLocked(mUnthemedEntries, configChanges);
-
- return mThemedEntries == null && mNullThemedEntries == null
- && mUnthemedEntries == null;
}
+
+ pruneEntriesLocked(mNullThemedEntries, configChanges);
+ pruneEntriesLocked(mUnthemedEntries, configChanges);
+
+ return mThemedEntries == null && mNullThemedEntries == null
+ && mUnthemedEntries == null;
}
private boolean pruneEntriesLocked(@Nullable LongSparseArray<WeakReference<T>> entries,
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 76efce5..022f3c4 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1762,6 +1762,24 @@
* 123,1,critical,0.8,default;123,1,moderate,0.6,id_2;456,2,moderate,0.9,critical,0.7
*/
String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data";
+
+ /**
+ * Key for new power controller feature flag. If enabled new DisplayPowerController will
+ * be used.
+ * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
+ * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace.
+ * @hide
+ */
+ String KEY_NEW_POWER_CONTROLLER = "use_newly_structured_display_power_controller";
+
+ /**
+ * Key for normal brightness mode controller feature flag.
+ * It enables NormalBrightnessModeController.
+ * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
+ * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace.
+ * @hide
+ */
+ String KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER = "use_normal_brightness_mode_controller";
}
/**
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 9d5073e..7080133 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -177,5 +177,5 @@
// Internal operation used to clear face biometric scheduler.
// Ensures that the scheduler is not stuck.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void scheduleWatchdog();
+ oneway void scheduleWatchdog();
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 9975852..e2840ec 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -213,5 +213,5 @@
// Internal operation used to clear fingerprint biometric scheduler.
// Ensures that the scheduler is not stuck.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void scheduleWatchdog();
+ oneway void scheduleWatchdog();
}
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index e6bdfe1..7664bad 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -112,9 +112,17 @@
private static final int ANGLE_GL_DRIVER_ALL_ANGLE_OFF = 0;
// Values for ANGLE_GL_DRIVER_SELECTION_VALUES
- private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default";
- private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
- private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
+ private enum AngleDriverChoice {
+ DEFAULT("default"),
+ ANGLE("angle"),
+ NATIVE("native");
+
+ public final String choice;
+
+ AngleDriverChoice(String choice) {
+ this.choice = choice;
+ }
+ }
private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported";
@@ -195,15 +203,16 @@
}
/**
- * Query to determine if ANGLE should be used
+ * Query to determine the ANGLE driver choice.
*/
- private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) {
+ private AngleDriverChoice queryAngleChoice(Context context, Bundle coreSettings,
+ String packageName) {
if (TextUtils.isEmpty(packageName)) {
Log.v(TAG, "No package name specified; use the system driver");
- return false;
+ return AngleDriverChoice.DEFAULT;
}
- return shouldUseAngleInternal(context, coreSettings, packageName);
+ return queryAngleChoiceInternal(context, coreSettings, packageName);
}
private int getVulkanVersion(PackageManager pm) {
@@ -424,10 +433,11 @@
* forces a choice;
* 3) Use ANGLE if isAngleEnabledByGameMode() returns true;
*/
- private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) {
+ private AngleDriverChoice queryAngleChoiceInternal(Context context, Bundle bundle,
+ String packageName) {
// Make sure we have a good package name
if (TextUtils.isEmpty(packageName)) {
- return false;
+ return AngleDriverChoice.DEFAULT;
}
// Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE
@@ -442,7 +452,7 @@
}
if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) {
Log.v(TAG, "Turn on ANGLE for all applications.");
- return true;
+ return AngleDriverChoice.ANGLE;
}
// Get the per-application settings lists
@@ -465,7 +475,7 @@
+ optInPackages.size() + ", "
+ "number of values: "
+ optInValues.size());
- return mEnabledByGameMode;
+ return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT;
}
// See if this application is listed in the per-application settings list
@@ -473,7 +483,7 @@
if (pkgIndex < 0) {
Log.v(TAG, packageName + " is not listed in per-application setting");
- return mEnabledByGameMode;
+ return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT;
}
mAngleOptInIndex = pkgIndex;
@@ -483,14 +493,14 @@
Log.v(TAG,
"ANGLE Developer option for '" + packageName + "' "
+ "set to: '" + optInValue + "'");
- if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) {
- return true;
- } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
- return false;
+ if (optInValue.equals(AngleDriverChoice.ANGLE.choice)) {
+ return AngleDriverChoice.ANGLE;
+ } else if (optInValue.equals(AngleDriverChoice.NATIVE.choice)) {
+ return AngleDriverChoice.NATIVE;
} else {
// The user either chose default or an invalid value; go with the default driver or what
// the game mode indicates
- return mEnabledByGameMode;
+ return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT;
}
}
@@ -558,7 +568,13 @@
private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager,
String packageName) {
- if (!shouldUseAngle(context, bundle, packageName)) {
+ final AngleDriverChoice angleDriverChoice = queryAngleChoice(context, bundle, packageName);
+ if (angleDriverChoice == AngleDriverChoice.DEFAULT) {
+ return false;
+ }
+
+ if (queryAngleChoice(context, bundle, packageName) == AngleDriverChoice.NATIVE) {
+ nativeSetAngleInfo("", true, packageName, null);
return false;
}
@@ -627,10 +643,10 @@
Log.d(TAG, "ANGLE package libs: " + paths);
}
- // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name,
- // and features to use.
+ // If we make it to here, ANGLE apk will be used. Call nativeSetAngleInfo() with the
+ // application package name and ANGLE features to use.
final String[] features = getAngleEglFeatures(context, bundle);
- setAngleInfo(paths, false, packageName, features);
+ nativeSetAngleInfo(paths, false, packageName, features);
return true;
}
@@ -652,10 +668,10 @@
return false;
}
- // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name,
- // and features to use.
+ // If we make it to here, system ANGLE will be used. Call nativeSetAngleInfo() with
+ // the application package name and ANGLE features to use.
final String[] features = getAngleEglFeatures(context, bundle);
- setAngleInfo("", true, packageName, features);
+ nativeSetAngleInfo("system", false, packageName, features);
return true;
}
@@ -936,8 +952,8 @@
private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries);
private static native void setGpuStats(String driverPackageName, String driverVersionName,
long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
- private static native void setAngleInfo(String path, boolean useSystemAngle, String packageName,
- String[] features);
+ private static native void nativeSetAngleInfo(String path, boolean useNativeDriver,
+ String packageName, String[] features);
private static native boolean setInjectLayersPrSetDumpable();
private static native void nativeToggleAngleAsSystemDriver(boolean enabled);
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 5e7f5d6..9b19937 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -26,7 +26,6 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.TestApi;
import android.app.Activity;
-import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.Service;
import android.compat.annotation.UnsupportedAppUsage;
@@ -1268,9 +1267,7 @@
fetchDreamLabel(this, serviceInfo, isPreviewMode));
try {
- if (!ActivityTaskManager.getService().startDreamActivity(i)) {
- detach();
- }
+ mDreamManager.startDreamActivity(i);
} catch (SecurityException e) {
Log.w(mTag,
"Received SecurityException trying to start DreamActivity. "
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 609425c..dd8b3de 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -17,6 +17,7 @@
package android.service.dreams;
import android.content.ComponentName;
+import android.content.Intent;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.IBinder;
@@ -45,4 +46,5 @@
void setDreamComponentsForUser(int userId, in ComponentName[] componentNames);
void setSystemDreamComponent(in ComponentName componentName);
void registerDreamOverlayService(in ComponentName componentName);
+ void startDreamActivity(in Intent intent);
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 6371da4..ab9cff0 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -25,7 +25,6 @@
import android.graphics.text.LineBreakConfig;
import android.graphics.text.LineBreaker;
import android.os.Build;
-import android.os.SystemProperties;
import android.text.style.LeadingMarginSpan;
import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
import android.text.style.LineHeightSpan;
@@ -33,7 +32,6 @@
import android.util.Log;
import android.util.Pools.SynchronizedPool;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
@@ -75,13 +73,6 @@
* default values.
*/
public final static class Builder {
- // The content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE.
- private static final int DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE = 3;
-
- // The property of content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE.
- private static final String PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE =
- "android.phrase.linecount.threshold";
-
private Builder() {}
/**
@@ -440,55 +431,11 @@
*/
@NonNull
public StaticLayout build() {
- reviseLineBreakConfig();
StaticLayout result = new StaticLayout(this);
Builder.recycle(this);
return result;
}
- private void reviseLineBreakConfig() {
- boolean autoPhraseBreaking = mLineBreakConfig.getAutoPhraseBreaking();
- int wordStyle = mLineBreakConfig.getLineBreakWordStyle();
- if (autoPhraseBreaking) {
- if (wordStyle != LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) {
- if (shouldEnablePhraseBreaking()) {
- mLineBreakConfig = LineBreakConfig.getLineBreakConfig(
- mLineBreakConfig.getLineBreakStyle(),
- LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE,
- mLineBreakConfig.getAutoPhraseBreaking());
- }
- }
- }
- }
-
- private boolean shouldEnablePhraseBreaking() {
- if (TextUtils.isEmpty(mText) || mWidth <= 0) {
- return false;
- }
- int lineLimit = SystemProperties.getInt(
- PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE,
- DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE);
- double desiredWidth = (double) Layout.getDesiredWidth(mText, mStart,
- mEnd, mPaint, mTextDir);
- int lineCount = (int) Math.ceil(desiredWidth / mWidth);
- if (lineCount > 0 && lineCount <= lineLimit) {
- return true;
- }
- return false;
- }
-
- /**
- * Get the line break word style.
- *
- * @return The current line break word style.
- *
- * @hide
- */
- @VisibleForTesting
- public int getLineBreakWordStyle() {
- return mLineBreakConfig.getLineBreakWordStyle();
- }
-
private CharSequence mText;
private int mStart;
private int mEnd;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index ff7d8bb..0071a0d 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -86,9 +86,6 @@
public static final String SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST =
"settings_need_connected_ble_device_for_broadcast";
- /** @hide */
- public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping";
-
/**
* Enable new language and keyboard settings UI
* @hide
@@ -225,7 +222,6 @@
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true");
- DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true");
@@ -253,7 +249,6 @@
PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
- PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 369c018..1d58268 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -847,7 +847,6 @@
return mLastDispatchedState;
}
- @VisibleForTesting
public boolean onStateChanged(InsetsState state) {
boolean stateChanged = false;
if (!CAPTION_ON_SHELL) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 1e268be..5b69d7f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1340,7 +1340,7 @@
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
.setName(name)
.setLocalOwnerView(this)
- .setParent(viewRoot.getBoundsLayer())
+ .setParent(viewRoot.updateAndGetBoundsLayer(surfaceUpdateTransaction))
.setCallsite("SurfaceView.updateSurface")
.setContainerLayer()
.build();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9316dbf..9d2bb58 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -716,9 +716,9 @@
/**
* Child container layer of {@code mSurface} with the same bounds as its parent, and cropped to
- * the surface insets. This surface is created only if a client requests it via {@link
- * #getBoundsLayer()}. By parenting to this bounds surface, child surfaces can ensure they do
- * not draw into the surface inset region set by the parent window.
+ * the surface insets. This surface is created only if a client requests it via
+ * {@link #updateAndGetBoundsLayer(Transaction)}. By parenting to this bounds surface, child
+ * surfaces can ensure they do not draw into the surface inset region set by the parent window.
*/
private SurfaceControl mBoundsLayer;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
@@ -2262,7 +2262,7 @@
* <p>Parenting to this layer will ensure that its children are cropped by the view's surface
* insets.
*/
- public SurfaceControl getBoundsLayer() {
+ public SurfaceControl updateAndGetBoundsLayer(Transaction t) {
if (mBoundsLayer == null) {
mBoundsLayer = new SurfaceControl.Builder(mSurfaceSession)
.setContainerLayer()
@@ -2270,8 +2270,8 @@
.setParent(getSurfaceControl())
.setCallsite("ViewRootImpl.getBoundsLayer")
.build();
- setBoundsLayerCrop(mTransaction);
- mTransaction.show(mBoundsLayer).apply();
+ setBoundsLayerCrop(t);
+ t.show(mBoundsLayer);
}
return mBoundsLayer;
}
@@ -3855,7 +3855,15 @@
mWmsRequestSyncGroupState = WMS_SYNC_PENDING;
mWmsRequestSyncGroup = new SurfaceSyncGroup("wmsSync-" + mTag, t -> {
mWmsRequestSyncGroupState = WMS_SYNC_MERGED;
- reportDrawFinished(t, seqId);
+ // See b/286355097. If the current process is not system, then invoking finishDraw on
+ // any thread is fine since once it calls into system process, finishDrawing will run
+ // on a different thread. However, when the current process is system, the finishDraw in
+ // system server will be run on the current thread, which could result in a deadlock.
+ if (mWindowSession instanceof Binder) {
+ reportDrawFinished(t, seqId);
+ } else {
+ mHandler.postAtFrontOfQueue(() -> reportDrawFinished(t, seqId));
+ }
});
if (DEBUG_BLAST) {
Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName());
@@ -11180,7 +11188,8 @@
@Nullable public SurfaceControl.Transaction buildReparentTransaction(
@NonNull SurfaceControl child) {
if (mSurfaceControl.isValid()) {
- return new SurfaceControl.Transaction().reparent(child, getBoundsLayer());
+ Transaction t = new Transaction();
+ return t.reparent(child, updateAndGetBoundsLayer(t));
}
return null;
}
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
index 8a30f8c..a11c6d0 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
@@ -115,4 +115,13 @@
* @param callback the interface to be called.
*/
void setConnectionCallback(in IWindowMagnificationConnectionCallback callback);
+
+ /**
+ * Notify System UI the magnification scale on the specified display for userId is changed.
+ *
+ * @param userId the user id.
+ * @param displayId the logical display id.
+ * @param scale magnification scale.
+ */
+ void onUserMagnificationScaleChanged(int userId, int displayId, float scale);
}
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index aa9225b..e9d7b9b 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -1118,7 +1118,7 @@
@InputConnection.CursorUpdateFilter int cursorUpdateFilter, int imeDisplayId) {
final InputConnection ic = getInputConnection();
if (ic == null || !isActive()) {
- Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
+ Log.w(TAG, "requestCursorUpdates on inactive InputConnection");
return false;
}
if (mParentInputMethodManager.mRequestCursorUpdateDisplayIdCheck.get()
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 438b974..27d8a8f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -156,7 +156,6 @@
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
-import android.util.FeatureFlagUtils;
import android.util.IntArray;
import android.util.Log;
import android.util.SparseIntArray;
@@ -831,11 +830,6 @@
private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
- // The auto option for LINE_BREAK_WORD_STYLE_PHRASE may not be applied in recycled view due to
- // one-way flag flipping. This is a tentative limitation during experiment and will not have the
- // issue once this is finalized to LINE_BREAK_WORD_STYLE_PHRASE_AUTO option.
- private boolean mUserSpeficiedLineBreakwordStyle = false;
-
// This is used to reflect the current user preference for changing font weight and making text
// more bold.
private int mFontWeightAdjustment;
@@ -1546,9 +1540,6 @@
break;
case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
- if (a.hasValue(attr)) {
- mUserSpeficiedLineBreakwordStyle = true;
- }
mLineBreakWordStyle = a.getInt(attr,
LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE);
break;
@@ -4350,7 +4341,6 @@
break;
case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle:
attributes.mHasLineBreakWordStyle = true;
- mUserSpeficiedLineBreakwordStyle = true;
attributes.mLineBreakWordStyle =
appearance.getInt(attr, attributes.mLineBreakWordStyle);
break;
@@ -5086,7 +5076,6 @@
* @param lineBreakWordStyle The line-break word style for the text.
*/
public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
- mUserSpeficiedLineBreakwordStyle = true;
if (mLineBreakWordStyle != lineBreakWordStyle) {
mLineBreakWordStyle = lineBreakWordStyle;
if (mLayout != null) {
@@ -5122,12 +5111,8 @@
* @see PrecomputedText
*/
public @NonNull PrecomputedText.Params getTextMetricsParams() {
- final boolean autoPhraseBreaking =
- !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
return new PrecomputedText.Params(new TextPaint(mTextPaint),
- LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle,
- autoPhraseBreaking),
+ LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle),
getTextDirectionHeuristic(),
mBreakStrategy, mHyphenationFrequency);
}
@@ -5147,7 +5132,6 @@
LineBreakConfig lineBreakConfig = params.getLineBreakConfig();
mLineBreakStyle = lineBreakConfig.getLineBreakStyle();
mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle();
- mUserSpeficiedLineBreakwordStyle = true;
if (mLayout != null) {
nullLayouts();
requestLayout();
@@ -7077,13 +7061,10 @@
if (mTextDir == null) {
mTextDir = getTextDirectionHeuristic();
}
- final boolean autoPhraseBreaking =
- !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
mHyphenationFrequency, LineBreakConfig.getLineBreakConfig(
- mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+ mLineBreakStyle, mLineBreakWordStyle));
switch (checkResult) {
case PrecomputedText.Params.UNUSABLE:
throw new IllegalArgumentException(
@@ -10640,9 +10621,6 @@
}
// TODO: code duplication with makeSingleLayout()
if (mHintLayout == null) {
- final boolean autoPhraseBreaking =
- !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
mHint.length(), mTextPaint, hintWidth)
.setAlignment(alignment)
@@ -10655,7 +10633,7 @@
.setJustificationMode(mJustificationMode)
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
- mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+ mLineBreakStyle, mLineBreakWordStyle));
if (shouldEllipsize) {
builder.setEllipsize(mEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -10759,9 +10737,6 @@
}
}
if (result == null) {
- final boolean autoPhraseBreaking =
- !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
0, mTransformed.length(), mTextPaint, wantWidth)
.setAlignment(alignment)
@@ -10774,7 +10749,7 @@
.setJustificationMode(mJustificationMode)
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
- mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+ mLineBreakStyle, mLineBreakWordStyle));
if (shouldEllipsize) {
builder.setEllipsize(effectiveEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -11132,9 +11107,6 @@
final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right));
- final boolean autoPhraseBreaking =
- !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
layoutBuilder.setAlignment(getLayoutAlignment())
.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
.setIncludePad(getIncludeFontPadding())
@@ -11145,7 +11117,7 @@
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setTextDirection(getTextDirectionHeuristic())
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
- mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+ mLineBreakStyle, mLineBreakWordStyle));
final StaticLayout layout = layoutBuilder.build();
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 9f804b1..3323ae5 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -688,6 +688,7 @@
.setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type)
.setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE)
.setArbitraryRectangle(frame))
+ .setInsetsFrameOwner(owner)
.build();
mHierarchyOps.add(hierarchyOp);
return this;
@@ -712,6 +713,7 @@
new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER)
.setContainer(receiver.asBinder())
.setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type))
+ .setInsetsFrameOwner(owner)
.build();
mHierarchyOps.add(hierarchyOp);
return this;
@@ -1344,8 +1346,12 @@
@Nullable
private IBinder mReparent;
+ @Nullable
private InsetsFrameProvider mInsetsFrameProvider;
+ @Nullable
+ private IBinder mInsetsFrameOwner;
+
// Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
private boolean mToTop;
@@ -1478,6 +1484,7 @@
mContainer = copy.mContainer;
mReparent = copy.mReparent;
mInsetsFrameProvider = copy.mInsetsFrameProvider;
+ mInsetsFrameOwner = copy.mInsetsFrameOwner;
mToTop = copy.mToTop;
mReparentTopOnly = copy.mReparentTopOnly;
mWindowingModes = copy.mWindowingModes;
@@ -1496,6 +1503,7 @@
mContainer = in.readStrongBinder();
mReparent = in.readStrongBinder();
mInsetsFrameProvider = in.readTypedObject(InsetsFrameProvider.CREATOR);
+ mInsetsFrameOwner = in.readStrongBinder();
mToTop = in.readBoolean();
mReparentTopOnly = in.readBoolean();
mWindowingModes = in.createIntArray();
@@ -1527,6 +1535,11 @@
return mInsetsFrameProvider;
}
+ @Nullable
+ public IBinder getInsetsFrameOwner() {
+ return mInsetsFrameOwner;
+ }
+
@NonNull
public IBinder getContainer() {
return mContainer;
@@ -1657,7 +1670,8 @@
case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER:
case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER:
sb.append("container=").append(mContainer)
- .append(" provider=").append(mInsetsFrameProvider);
+ .append(" provider=").append(mInsetsFrameProvider)
+ .append(" owner=").append(mInsetsFrameOwner);
break;
case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
sb.append("container=").append(mContainer)
@@ -1697,6 +1711,7 @@
dest.writeStrongBinder(mContainer);
dest.writeStrongBinder(mReparent);
dest.writeTypedObject(mInsetsFrameProvider, flags);
+ dest.writeStrongBinder(mInsetsFrameOwner);
dest.writeBoolean(mToTop);
dest.writeBoolean(mReparentTopOnly);
dest.writeIntArray(mWindowingModes);
@@ -1737,8 +1752,12 @@
@Nullable
private IBinder mReparent;
+ @Nullable
private InsetsFrameProvider mInsetsFrameProvider;
+ @Nullable
+ private IBinder mInsetsFrameOwner;
+
private boolean mToTop;
private boolean mReparentTopOnly;
@@ -1782,8 +1801,13 @@
return this;
}
- Builder setInsetsFrameProvider(InsetsFrameProvider providers) {
- mInsetsFrameProvider = providers;
+ Builder setInsetsFrameProvider(InsetsFrameProvider provider) {
+ mInsetsFrameProvider = provider;
+ return this;
+ }
+
+ Builder setInsetsFrameOwner(IBinder owner) {
+ mInsetsFrameOwner = owner;
return this;
}
@@ -1854,6 +1878,7 @@
? Arrays.copyOf(mActivityTypes, mActivityTypes.length)
: null;
hierarchyOp.mInsetsFrameProvider = mInsetsFrameProvider;
+ hierarchyOp.mInsetsFrameOwner = mInsetsFrameOwner;
hierarchyOp.mToTop = mToTop;
hierarchyOp.mReparentTopOnly = mReparentTopOnly;
hierarchyOp.mLaunchOptions = mLaunchOptions;
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 954f686..2858f0a 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -145,13 +145,13 @@
for (int i = 0; i < possibleDisplayInfos.size(); i++) {
currentDisplayInfo = possibleDisplayInfos.get(i);
- // Calculate max bounds for this rotation and state.
- Rect maxBounds = new Rect(0, 0, currentDisplayInfo.logicalWidth,
- currentDisplayInfo.logicalHeight);
+ // Calculate max bounds for natural rotation and state.
+ Rect maxBounds = new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
+ currentDisplayInfo.getNaturalHeight());
- // Calculate insets for the rotated max bounds.
+ // Calculate insets for the natural max bounds.
final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0;
- // Initialize insets based upon display rotation. Note any window-provided insets
+ // Initialize insets based on Surface.ROTATION_0. Note any window-provided insets
// will not be set.
windowInsets = getWindowInsetsFromServerForDisplay(
currentDisplayInfo.displayId, null /* token */,
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index f2ae973..611da3c 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -34,6 +34,7 @@
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.IBinder;
+import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams.WindowType;
@@ -52,6 +53,8 @@
@UiContext
public abstract class WindowProviderService extends Service implements WindowProvider {
+ private static final String TAG = WindowProviderService.class.getSimpleName();
+
private final Bundle mOptions;
private final WindowTokenClient mWindowToken = new WindowTokenClient();
private final WindowContextController mController = new WindowContextController(mWindowToken);
@@ -194,8 +197,16 @@
public final Context createServiceBaseContext(ActivityThread mainThread,
LoadedApk packageInfo) {
final Context context = super.createServiceBaseContext(mainThread, packageInfo);
- final Display display = context.getSystemService(DisplayManager.class)
- .getDisplay(getInitialDisplayId());
+ final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ final int initialDisplayId = getInitialDisplayId();
+ Display display = displayManager.getDisplay(initialDisplayId);
+ // Fallback to use the default display if the initial display to start WindowProviderService
+ // is detached.
+ if (display == null) {
+ Log.e(TAG, "Display with id " + initialDisplayId + " not found, falling back to "
+ + "DEFAULT_DISPLAY");
+ display = displayManager.getDisplay(DEFAULT_DISPLAY);
+ }
return context.createTokenContext(mWindowToken, display);
}
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
index 95c3419..2c49303 100644
--- a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
+++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
@@ -17,7 +17,7 @@
package com.android.internal.accessibility.common;
/**
- * Collection of common constants for accessibility shortcut.
+ * Collection of common constants for accessibility magnification.
*/
public final class MagnificationConstants {
private MagnificationConstants() {}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 7452daa..65b5979 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -56,6 +56,7 @@
import android.os.UserManager;
import android.provider.Settings;
import android.telecom.TelecomManager;
+import android.util.Log;
import android.util.Slog;
import android.view.View;
import android.widget.Button;
@@ -124,16 +125,19 @@
String className = intentReceived.getComponent().getClassName();
final int targetUserId;
final String userMessage;
+ final UserInfo managedProfile;
if (className.equals(FORWARD_INTENT_TO_PARENT)) {
userMessage = getForwardToPersonalMessage();
targetUserId = getProfileParent();
+ managedProfile = null;
getMetricsLogger().write(
new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE)
.setSubtype(MetricsEvent.PARENT_PROFILE));
} else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) {
userMessage = getForwardToWorkMessage();
- targetUserId = getManagedProfile();
+ managedProfile = getManagedProfile();
+ targetUserId = managedProfile == null ? UserHandle.USER_NULL : managedProfile.id;
getMetricsLogger().write(
new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE)
@@ -142,6 +146,7 @@
Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly");
userMessage = null;
targetUserId = UserHandle.USER_NULL;
+ managedProfile = null;
}
if (targetUserId == UserHandle.USER_NULL) {
// This covers the case where there is no parent / managed profile.
@@ -185,27 +190,49 @@
finish();
// When switching to the work profile, ask the user for consent before launching
} else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) {
- maybeShowUserConsentMiniResolver(result, newIntent, targetUserId);
+ maybeShowUserConsentMiniResolver(result, newIntent, managedProfile);
}
}, getApplicationContext().getMainExecutor());
}
private void maybeShowUserConsentMiniResolver(
- ResolveInfo target, Intent launchIntent, int targetUserId) {
+ ResolveInfo target, Intent launchIntent, UserInfo managedProfile) {
if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) {
finish();
return;
}
- if (launchIntent.getBooleanExtra(EXTRA_SKIP_USER_CONFIRMATION, /* defaultValue= */ false)
- && getCallingPackage() != null
- && PERMISSION_GRANTED == getPackageManager().checkPermission(
- INTERACT_ACROSS_USERS, getCallingPackage())) {
+ int targetUserId = managedProfile == null ? UserHandle.USER_NULL : managedProfile.id;
+ String callingPackage = getCallingPackage();
+ boolean privilegedCallerAskedToSkipUserConsent =
+ launchIntent.getBooleanExtra(
+ EXTRA_SKIP_USER_CONFIRMATION, /* defaultValue= */ false)
+ && callingPackage != null
+ && PERMISSION_GRANTED == getPackageManager().checkPermission(
+ INTERACT_ACROSS_USERS, callingPackage);
+
+ DevicePolicyManager devicePolicyManager =
+ getSystemService(DevicePolicyManager.class);
+ ComponentName profileOwnerName = devicePolicyManager.getProfileOwnerAsUser(targetUserId);
+ boolean intentToLaunchProfileOwner = profileOwnerName != null
+ && profileOwnerName.getPackageName().equals(target.getComponentInfo().packageName);
+
+ if (privilegedCallerAskedToSkipUserConsent || intentToLaunchProfileOwner) {
+ Log.i("IntentForwarderActivity", String.format(
+ "Skipping user consent for redirection into the managed profile for intent [%s]"
+ + ", privilegedCallerAskedToSkipUserConsent=[%s]"
+ + ", intentToLaunchProfileOwner=[%s]",
+ launchIntent, privilegedCallerAskedToSkipUserConsent,
+ intentToLaunchProfileOwner));
startActivityAsCaller(launchIntent, targetUserId);
finish();
return;
}
+ Log.i("IntentForwarderActivity", String.format(
+ "Showing user consent for redirection into the managed profile for intent [%s] and "
+ + " calling package [%s]",
+ launchIntent, callingPackage));
int layoutId = R.layout.miniresolver;
setContentView(layoutId);
@@ -245,8 +272,7 @@
View telephonyInfo = findViewById(R.id.miniresolver_info_section);
- DevicePolicyManager devicePolicyManager =
- getSystemService(DevicePolicyManager.class);
+
// Additional information section is work telephony specific. Therefore, it is only shown
// for telephony related intents, when all sim subscriptions are in the work profile.
if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
@@ -507,20 +533,18 @@
}
/**
- * Returns the userId of the managed profile for this device or UserHandle.USER_NULL if there is
- * no managed profile.
+ * Returns the managed profile for this device or null if there is no managed profile.
*
- * TODO: Remove the assumption that there is only one managed profile
- * on the device.
+ * TODO: Remove the assumption that there is only one managed profile on the device.
*/
- private int getManagedProfile() {
+ @Nullable private UserInfo getManagedProfile() {
List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId());
for (UserInfo userInfo : relatedUsers) {
- if (userInfo.isManagedProfile()) return userInfo.id;
+ if (userInfo.isManagedProfile()) return userInfo;
}
Slog.wtf(TAG, FORWARD_INTENT_TO_MANAGED_PROFILE
+ " has been called, but there is no managed profile");
- return UserHandle.USER_NULL;
+ return null;
}
/**
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index afc3cbd..8fc30d1 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -50,7 +50,7 @@
appPackageNameChars.c_str(), vulkanVersion);
}
-void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useSystemAngle,
+void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver,
jstring packageName, jobjectArray featuresObj) {
ScopedUtfChars pathChars(env, path);
ScopedUtfChars packageNameChars(env, packageName);
@@ -73,7 +73,7 @@
}
}
- android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useSystemAngle,
+ android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver,
packageNameChars.c_str(), features);
}
@@ -118,7 +118,7 @@
reinterpret_cast<void*>(setGpuStats_native)},
{"setInjectLayersPrSetDumpable", "()Z",
reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
- {"setAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
+ {"nativeSetAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
reinterpret_cast<void*>(setAngleInfo_native)},
{"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
reinterpret_cast<void*>(setLayerPaths_native)},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2f9f6ae..a01c7b6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6094,7 +6094,7 @@
<!-- @hide @SystemApi Allows an application to observe usage time of apps. The app can register
for callbacks when apps reach a certain usage time limit, etc. -->
<permission android:name="android.permission.OBSERVE_APP_USAGE"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|role" />
<!-- @hide @TestApi @SystemApi Allows an application to change the app idle state of an app.
<p>Not for use by third-party applications. -->
@@ -6724,7 +6724,7 @@
it will be ignored.
@hide -->
<permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|role" />
<!-- @SystemApi Allows entering or exiting car mode using a specified priority.
This permission is required to use UiModeManager while specifying a priority for the calling
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index eba7f58..7c69e65 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -271,6 +271,54 @@
}
@Test
+ public void allPendingIntents_resilientToAnotherNotificationInExtras() {
+ PendingIntent contentIntent = createPendingIntent("content");
+ PendingIntent actionIntent = createPendingIntent("action");
+ Notification another = new Notification.Builder(mContext, "channel").build();
+ Bundle bundleContainingAnotherNotification = new Bundle();
+ bundleContainingAnotherNotification.putParcelable(null, another);
+ Notification source = new Notification.Builder(mContext, "channel")
+ .setContentIntent(contentIntent)
+ .addAction(new Notification.Action.Builder(null, "action", actionIntent).build())
+ .setExtras(bundleContainingAnotherNotification)
+ .build();
+
+ Parcel p = Parcel.obtain();
+ source.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ Notification unparceled = new Notification(p);
+
+ assertThat(unparceled.allPendingIntents).containsExactly(contentIntent, actionIntent);
+ }
+
+ @Test
+ public void allPendingIntents_alsoInPublicVersion() {
+ PendingIntent contentIntent = createPendingIntent("content");
+ PendingIntent actionIntent = createPendingIntent("action");
+ PendingIntent publicContentIntent = createPendingIntent("publicContent");
+ PendingIntent publicActionIntent = createPendingIntent("publicAction");
+ Notification source = new Notification.Builder(mContext, "channel")
+ .setContentIntent(contentIntent)
+ .addAction(new Notification.Action.Builder(null, "action", actionIntent).build())
+ .setPublicVersion(new Notification.Builder(mContext, "channel")
+ .setContentIntent(publicContentIntent)
+ .addAction(new Notification.Action.Builder(
+ null, "publicAction", publicActionIntent).build())
+ .build())
+ .build();
+
+ Parcel p = Parcel.obtain();
+ source.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ Notification unparceled = new Notification(p);
+
+ assertThat(unparceled.allPendingIntents).containsExactly(contentIntent, actionIntent,
+ publicContentIntent, publicActionIntent);
+ assertThat(unparceled.publicVersion.allPendingIntents).containsExactly(publicContentIntent,
+ publicActionIntent);
+ }
+
+ @Test
public void messagingStyle_isGroupConversation() {
mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index 925da49..0ebf03f 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -24,7 +24,6 @@
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
-import android.graphics.text.LineBreakConfig;
import android.os.LocaleList;
import android.platform.test.annotations.Presubmit;
import android.text.Layout.Alignment;
@@ -926,24 +925,4 @@
assertEquals(0, layout.getHeight(true));
assertEquals(2, layout.getLineCount());
}
-
- @Test
- public void testBuilder_autoPhraseBreaking() {
- {
- // setAutoPhraseBreaking true
- LineBreakConfig lineBreakConfig = new LineBreakConfig.Builder()
- .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_STYLE_NONE)
- .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
- .setAutoPhraseBreaking(true)
- .build();
- final String text = "これが正解。";
- // Obtain.
- StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0,
- text.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
- builder.setLineBreakConfig(lineBreakConfig);
- builder.build();
- assertEquals(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE,
- builder.getLineBreakWordStyle());
- }
- }
}
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index d0327159..0c493f5 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -94,11 +94,6 @@
private @LineBreakWordStyle int mLineBreakWordStyle =
LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
- // Whether or not enabling phrase breaking automatically.
- // TODO(b/226012260): Remove this and add LINE_BREAK_WORD_STYLE_PHRASE_AUTO after
- // the experiment.
- private boolean mAutoPhraseBreaking = false;
-
/**
* Builder constructor.
*/
@@ -128,22 +123,12 @@
}
/**
- * Enables or disables the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}.
- *
- * @hide
- */
- public @NonNull Builder setAutoPhraseBreaking(boolean autoPhraseBreaking) {
- mAutoPhraseBreaking = autoPhraseBreaking;
- return this;
- }
-
- /**
* Builds a {@link LineBreakConfig} instance.
*
* @return The {@code LineBreakConfig} instance.
*/
public @NonNull LineBreakConfig build() {
- return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mAutoPhraseBreaking);
+ return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle);
}
}
@@ -164,23 +149,6 @@
.build();
}
- /**
- * Create the LineBreakConfig instance.
- *
- * @param lineBreakStyle the line break style for text wrapping.
- * @param lineBreakWordStyle the line break word style for text wrapping.
- * @return the {@link LineBreakConfig} instance. *
- * @hide
- */
- public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle,
- @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
- LineBreakConfig.Builder builder = new LineBreakConfig.Builder();
- return builder.setLineBreakStyle(lineBreakStyle)
- .setLineBreakWordStyle(lineBreakWordStyle)
- .setAutoPhraseBreaking(autoPhraseBreaking)
- .build();
- }
-
/** @hide */
public static final LineBreakConfig NONE =
new Builder().setLineBreakStyle(LINE_BREAK_STYLE_NONE)
@@ -188,7 +156,6 @@
private final @LineBreakStyle int mLineBreakStyle;
private final @LineBreakWordStyle int mLineBreakWordStyle;
- private final boolean mAutoPhraseBreaking;
/**
* Constructor with line-break parameters.
@@ -197,10 +164,9 @@
* {@code LineBreakConfig} instance.
*/
private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
- @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
+ @LineBreakWordStyle int lineBreakWordStyle) {
mLineBreakStyle = lineBreakStyle;
mLineBreakWordStyle = lineBreakWordStyle;
- mAutoPhraseBreaking = autoPhraseBreaking;
}
/**
@@ -221,17 +187,6 @@
return mLineBreakWordStyle;
}
- /**
- * Used to identify if the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled.
- *
- * @return The result that records whether or not the automation of
- * {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled.
- * @hide
- */
- public boolean getAutoPhraseBreaking() {
- return mAutoPhraseBreaking;
- }
-
@Override
public boolean equals(Object o) {
if (o == null) return false;
@@ -239,8 +194,7 @@
if (!(o instanceof LineBreakConfig)) return false;
LineBreakConfig that = (LineBreakConfig) o;
return (mLineBreakStyle == that.mLineBreakStyle)
- && (mLineBreakWordStyle == that.mLineBreakWordStyle)
- && (mAutoPhraseBreaking == that.mAutoPhraseBreaking);
+ && (mLineBreakWordStyle == that.mLineBreakWordStyle);
}
@Override
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 14e8253..3d4b55a 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -228,12 +228,12 @@
<dimen name="bubble_user_education_stack_padding">16dp</dimen>
<!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. -->
<dimen name="bubblebar_size">72dp</dimen>
- <!-- The size of the drag handle / menu shown along with a bubble bar expanded view. -->
- <dimen name="bubble_bar_expanded_view_handle_size">40dp</dimen>
- <!-- The width of the drag handle shown along with a bubble bar expanded view. -->
- <dimen name="bubble_bar_expanded_view_handle_width">128dp</dimen>
- <!-- The height of the drag handle shown along with a bubble bar expanded view. -->
- <dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen>
+ <!-- The size of the caption bar inset at the top of bubble bar expanded view. -->
+ <dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen>
+ <!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. -->
+ <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen>
+ <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. -->
+ <dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen>
<!-- Minimum width of the bubble bar manage menu. -->
<dimen name="bubble_bar_manage_menu_min_width">200dp</dimen>
<!-- Size of the dismiss icon in the bubble bar manage menu. -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index f729d02..e97390d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -25,6 +25,8 @@
import android.util.Log;
import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
+
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.bubbles.BubbleOverflow;
@@ -111,7 +113,8 @@
/**
* Animates the provided bubble's expanded view to the expanded state.
*/
- public void animateExpansion(BubbleViewProvider expandedBubble) {
+ public void animateExpansion(BubbleViewProvider expandedBubble,
+ @Nullable Runnable afterAnimation) {
mExpandedBubble = expandedBubble;
if (mExpandedBubble == null) {
return;
@@ -160,6 +163,9 @@
bev.setAnimationMatrix(null);
updateExpandedView();
bev.setSurfaceZOrderedOnTop(false);
+ if (afterAnimation != null) {
+ afterAnimation.run();
+ }
})
.start();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 396aa0e..6b6d6ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -16,12 +16,12 @@
package com.android.wm.shell.bubbles.bar;
-import android.annotation.ColorInt;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
+import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -63,7 +63,8 @@
private @Nullable TaskView mTaskView;
private @Nullable BubbleOverflowContainerView mOverflowView;
- private int mHandleHeight;
+ private int mCaptionHeight;
+
private int mBackgroundColor;
private float mCornerRadius = 0f;
@@ -97,8 +98,8 @@
super.onFinishInflate();
Context context = getContext();
setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation));
- mHandleHeight = context.getResources().getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_handle_size);
+ mCaptionHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_height);
addView(mHandleView);
applyThemeAttrs();
setClipToOutline(true);
@@ -136,6 +137,9 @@
addView(mTaskView);
mTaskView.setEnableSurfaceClipping(true);
mTaskView.setCornerRadius(mCornerRadius);
+
+ // Handle view needs to draw on top of task view.
+ bringChildToFront(mHandleView);
}
mMenuViewController = new BubbleBarMenuViewController(mContext, this);
mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
@@ -169,6 +173,10 @@
});
}
+ public BubbleBarHandleView getHandleView() {
+ return mHandleView;
+ }
+
// TODO (b/275087636): call this when theme/config changes
/** Updates the view based on the current theme. */
public void applyThemeAttrs() {
@@ -183,12 +191,12 @@
ta.recycle();
- mHandleHeight = getResources().getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_handle_size);
+ mCaptionHeight = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_height);
if (mTaskView != null) {
mTaskView.setCornerRadius(mCornerRadius);
- updateHandleAndBackgroundColor(true /* animated */);
+ updateHandleColor(true /* animated */);
}
}
@@ -196,13 +204,12 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
- int menuViewHeight = Math.min(mHandleHeight, height);
+ int menuViewHeight = Math.min(mCaptionHeight, height);
measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
MeasureSpec.getMode(heightMeasureSpec)));
if (mTaskView != null) {
- int taskViewHeight = height - menuViewHeight;
- measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight,
+ measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(height,
MeasureSpec.getMode(heightMeasureSpec)));
}
}
@@ -210,19 +217,20 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- // Drag handle above
- final int dragHandleBottom = t + mHandleView.getMeasuredHeight();
- mHandleView.layout(l, t, r, dragHandleBottom);
+ final int captionBottom = t + mCaptionHeight;
if (mTaskView != null) {
- mTaskView.layout(l, dragHandleBottom, r,
- dragHandleBottom + mTaskView.getMeasuredHeight());
+ mTaskView.layout(l, t, r,
+ t + mTaskView.getMeasuredHeight());
+ mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
}
+ // Handle draws on top of task view in the caption area.
+ mHandleView.layout(l, t, r, captionBottom);
}
@Override
public void onTaskCreated() {
setContentVisibility(true);
- updateHandleAndBackgroundColor(false /* animated */);
+ updateHandleColor(false /* animated */);
}
@Override
@@ -298,33 +306,20 @@
}
/**
- * Updates the background color to match with task view status/bg color, and sets handle color
- * to contrast with the background
+ * Updates the handle color based on the task view status bar or background color; if those
+ * are transparent it defaults to the background color pulled from system theme attributes.
*/
- private void updateHandleAndBackgroundColor(boolean animated) {
- if (mTaskView == null) return;
- final int color = getTaskViewColor();
- final boolean isRegionDark = Color.luminance(color) <= 0.5;
- mHandleView.updateHandleColor(isRegionDark, animated);
- setBackgroundColor(color);
- }
-
- /**
- * Retrieves task view status/nav bar color or background if available
- *
- * TODO (b/283075226): Update with color sampling when
- * RegionSamplingHelper or alternative is available
- */
- private @ColorInt int getTaskViewColor() {
- if (mTaskView == null || mTaskView.getTaskInfo() == null) return mBackgroundColor;
+ private void updateHandleColor(boolean animated) {
+ if (mTaskView == null || mTaskView.getTaskInfo() == null) return;
+ int color = mBackgroundColor;
ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription;
if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) {
- return taskDescription.getStatusBarColor();
+ color = taskDescription.getStatusBarColor();
} else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) {
- return taskDescription.getBackgroundColor();
- } else {
- return mBackgroundColor;
+ color = taskDescription.getBackgroundColor();
}
+ final boolean isRegionDark = Color.luminance(color) <= 0.5;
+ mHandleView.updateHandleColor(isRegionDark, animated);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index ce26bc0..2b7a070 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -21,7 +21,8 @@
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Outline;
-import android.graphics.Rect;
+import android.graphics.Path;
+import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
@@ -37,8 +38,12 @@
public class BubbleBarHandleView extends View {
private static final long COLOR_CHANGE_DURATION = 120;
- private int mHandleWidth;
- private int mHandleHeight;
+ // The handle view is currently rendered as 3 evenly spaced dots.
+ private int mDotSize;
+ private int mDotSpacing;
+ // Path used to draw the dots
+ private final Path mPath = new Path();
+
private @ColorInt int mHandleLightColor;
private @ColorInt int mHandleDarkColor;
private @Nullable ObjectAnimator mColorChangeAnim;
@@ -58,11 +63,10 @@
public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
-
- mHandleWidth = getResources().getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_handle_width);
- mHandleHeight = getResources().getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_handle_height);
+ mDotSize = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_dot_size);
+ mDotSpacing = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_dot_spacing);
mHandleLightColor = ContextCompat.getColor(getContext(),
R.color.bubble_bar_expanded_view_handle_light);
mHandleDarkColor = ContextCompat.getColor(getContext(),
@@ -74,13 +78,26 @@
public void getOutline(View view, Outline outline) {
final int handleCenterX = view.getWidth() / 2;
final int handleCenterY = view.getHeight() / 2;
- final float handleRadius = mHandleHeight / 2f;
- Rect handleBounds = new Rect(
- handleCenterX - mHandleWidth / 2,
- handleCenterY - mHandleHeight / 2,
- handleCenterX + mHandleWidth / 2,
- handleCenterY + mHandleHeight / 2);
- outline.setRoundRect(handleBounds, handleRadius);
+ final int handleTotalWidth = mDotSize * 3 + mDotSpacing * 2;
+ final int handleLeft = handleCenterX - handleTotalWidth / 2;
+ final int handleTop = handleCenterY - mDotSize / 2;
+ final int handleBottom = handleTop + mDotSize;
+ RectF dot1 = new RectF(
+ handleLeft, handleTop,
+ handleLeft + mDotSize, handleBottom);
+ RectF dot2 = new RectF(
+ dot1.right + mDotSpacing, handleTop,
+ dot1.right + mDotSpacing + mDotSize, handleBottom
+ );
+ RectF dot3 = new RectF(
+ dot2.right + mDotSpacing, handleTop,
+ dot2.right + mDotSpacing + mDotSize, handleBottom
+ );
+ mPath.reset();
+ mPath.addOval(dot1, Path.Direction.CW);
+ mPath.addOval(dot2, Path.Direction.CW);
+ mPath.addOval(dot3, Path.Direction.CW);
+ outline.setPath(mPath);
}
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index d20b33e..8ead18b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -24,6 +24,7 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
+import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
@@ -68,6 +69,10 @@
private final Region mTouchableRegion = new Region();
private final Rect mTempRect = new Rect();
+ // Used to ensure touch target size for the menu shown on a bubble expanded view
+ private TouchDelegate mHandleTouchDelegate;
+ private final Rect mHandleTouchBounds = new Rect();
+
public BubbleBarLayerView(Context context, BubbleController controller) {
super(context);
mBubbleController = controller;
@@ -164,7 +169,17 @@
mIsExpanded = true;
mBubbleController.getSysuiProxy().onStackExpandChanged(true);
- mAnimationHelper.animateExpansion(mExpandedBubble);
+ mAnimationHelper.animateExpansion(mExpandedBubble, () -> {
+ if (mExpandedView == null) return;
+ // Touch delegate for the menu
+ BubbleBarHandleView view = mExpandedView.getHandleView();
+ view.getBoundsOnScreen(mHandleTouchBounds);
+ mHandleTouchBounds.top -= mPositioner.getBubblePaddingTop();
+ mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds,
+ mExpandedView.getHandleView());
+ setTouchDelegate(mHandleTouchDelegate);
+ });
+
showScrim(true);
}
@@ -175,6 +190,7 @@
mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
mBubbleController.getSysuiProxy().onStackExpandChanged(false);
mExpandedView = null;
+ setTouchDelegate(null);
showScrim(false);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 262d487..2dbc444 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -434,7 +434,7 @@
"Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
from);
mInteractive = interactive;
- if (!mInteractive && mMoving) {
+ if (!mInteractive && hideHandle && mMoving) {
final int position = mSplitLayout.getDividePosition();
mSplitLayout.flingDividePosition(
mLastDraggingPosition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index a9ccdf6..2b10377 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -323,7 +323,11 @@
}
}
if (mShown) {
- fadeOutDecor(()-> animFinishedCallback.accept(true));
+ fadeOutDecor(()-> {
+ if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+ animFinishedCallback.accept(true);
+ }
+ });
} else {
// Decor surface is hidden so release it directly.
releaseDecor(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index f70d3ae..e8fa638 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -593,9 +593,6 @@
void flingDividePosition(int from, int to, int duration,
@Nullable Runnable flingFinishedCallback) {
if (from == to) {
- // No animation run, still callback to stop resizing.
- mSplitLayoutHandler.onLayoutSizeChanged(this);
-
if (flingFinishedCallback != null) {
flingFinishedCallback.run();
}
@@ -773,15 +770,13 @@
ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
boolean boundsChanged = false;
if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
- wct.setBounds(task1.token, mBounds1);
- wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
+ setTaskBounds(wct, task1, mBounds1);
mWinBounds1.set(mBounds1);
mWinToken1 = task1.token;
boundsChanged = true;
}
if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
- wct.setBounds(task2.token, mBounds2);
- wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
+ setTaskBounds(wct, task2, mBounds2);
mWinBounds2.set(mBounds2);
mWinToken2 = task2.token;
boundsChanged = true;
@@ -789,6 +784,13 @@
return boundsChanged;
}
+ /** Set bounds to the {@link WindowContainerTransaction} for single task. */
+ public void setTaskBounds(WindowContainerTransaction wct,
+ ActivityManager.RunningTaskInfo task, Rect bounds) {
+ wct.setBounds(task.token, bounds);
+ wct.setSmallestScreenWidthDp(task.token, getSmallestWidthDp(bounds));
+ }
+
private int getSmallestWidthDp(Rect bounds) {
mTempRect.set(bounds);
mTempRect.inset(getDisplayStableInsets(mContext));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index 0289da9..d7ea1c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -89,4 +89,9 @@
int userId1, int userId2) {
return (packageName1 != null && packageName1.equals(packageName2)) && (userId1 == userId2);
}
+
+ /** Generates a common log message for split screen failures */
+ public static String splitFailureMessage(String caller, String reason) {
+ return "(" + caller + ") Splitscreen aborted: " + reason;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 4fda4b7..6d14440 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -63,6 +63,7 @@
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -204,7 +205,11 @@
* Moves a single task to freeform and sets the taskBounds to the passed in bounds,
* startBounds
*/
- fun moveToFreeform(taskInfo: RunningTaskInfo, startBounds: Rect) {
+ fun moveToFreeform(
+ taskInfo: RunningTaskInfo,
+ startBounds: Rect,
+ dragToDesktopValueAnimator: MoveToDesktopAnimator
+ ) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToFreeform with bounds taskId=%d",
@@ -216,8 +221,8 @@
wct.setBounds(taskInfo.token, startBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startTransition(
- Transitions.TRANSIT_ENTER_FREEFORM, wct, mOnAnimationFinishedCallback)
+ enterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct,
+ dragToDesktopValueAnimator, mOnAnimationFinishedCallback)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
@@ -270,7 +275,7 @@
* Move a task to fullscreen after being dragged from fullscreen and released back into
* status bar area
*/
- fun cancelMoveToFreeform(task: RunningTaskInfo, position: Point) {
+ fun cancelMoveToFreeform(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: cancelMoveToFreeform taskId=%d",
@@ -280,13 +285,13 @@
wct.setBounds(task.token, null)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(
- wct, position) { t ->
+ enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct,
+ moveToDesktopAnimator) { t ->
val callbackWCT = WindowContainerTransaction()
visualIndicator?.releaseVisualIndicator(t)
visualIndicator = null
addMoveToFullscreenChanges(callbackWCT, task)
- shellTaskOrganizer.applyTransaction(callbackWCT)
+ transitions.startTransition(TRANSIT_CHANGE, callbackWCT, null /* handler */)
}
} else {
addMoveToFullscreenChanges(wct, task)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 3e175f3..650cac5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -22,9 +22,10 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
-import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
+import android.util.Slog;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -35,6 +36,7 @@
import androidx.annotation.Nullable;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
import java.util.ArrayList;
import java.util.List;
@@ -47,18 +49,17 @@
*/
public class EnterDesktopTaskTransitionHandler implements Transitions.TransitionHandler {
+ private static final String TAG = "EnterDesktopTaskTransitionHandler";
private final Transitions mTransitions;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
- // The size of the screen during drag relative to the fullscreen size
- public static final float DRAG_FREEFORM_SCALE = 0.4f;
// The size of the screen after drag relative to the fullscreen size
public static final float FINAL_FREEFORM_SCALE = 0.6f;
public static final int FREEFORM_ANIMATION_DURATION = 336;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
- private Point mPosition;
private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
+ private MoveToDesktopAnimator mMoveToDesktopAnimator;
public EnterDesktopTaskTransitionHandler(
Transitions transitions) {
@@ -87,15 +88,30 @@
}
/**
+ * Starts Transition of type TRANSIT_ENTER_FREEFORM
+ * @param wct WindowContainerTransaction for transition
+ * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
+ * to desktop animation
+ * @param onAnimationEndCallback to be called after animation
+ */
+ public void startMoveToFreeformAnimation(@NonNull WindowContainerTransaction wct,
+ @NonNull MoveToDesktopAnimator moveToDesktopAnimator,
+ Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+ mMoveToDesktopAnimator = moveToDesktopAnimator;
+ startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct, onAnimationEndCallback);
+ }
+
+ /**
* Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
* @param wct WindowContainerTransaction for transition
- * @param position Position of task when transition is triggered
+ * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
+ * to desktop animation
* @param onAnimationEndCallback to be called after animation
*/
public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
- Point position,
+ MoveToDesktopAnimator moveToDesktopAnimator,
Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
- mPosition = position;
+ mMoveToDesktopAnimator = moveToDesktopAnimator;
startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct,
onAnimationEndCallback);
}
@@ -145,9 +161,23 @@
// to null and we don't require an animation
final SurfaceControl sc = change.getLeash();
startT.setWindowCrop(sc, null);
+
+ if (mMoveToDesktopAnimator == null
+ || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+ Slog.e(TAG, "No animator available for this transition");
+ return false;
+ }
+
+ // Calculate and set position of the task
+ final PointF position = mMoveToDesktopAnimator.getPosition();
+ startT.setPosition(sc, position.x, position.y);
+ finishT.setPosition(sc, position.x, position.y);
+
startT.apply();
+
mTransitions.getMainExecutor().execute(
() -> finishCallback.onTransitionFinished(null, null));
+
return true;
}
@@ -162,12 +192,18 @@
endBounds.height());
startT.apply();
+ // End the animation that shrinks the window when task is first dragged from fullscreen
+ if (mMoveToDesktopAnimator != null) {
+ mMoveToDesktopAnimator.endAnimator();
+ }
+
// We want to find the scale of the current bounds relative to the end bounds. The
// task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be
// scaled to FINAL_FREEFORM_SCALE. So, it is scaled to
// DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds
final ValueAnimator animator =
- ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
+ ValueAnimator.ofFloat(
+ MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
animator.setDuration(FREEFORM_ANIMATION_DURATION);
final SurfaceControl.Transaction t = mTransactionSupplier.get();
animator.addUpdateListener(animation -> {
@@ -199,8 +235,7 @@
}
if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- && mPosition != null) {
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
// This Transition animates a task to fullscreen after being dragged from the status
// bar and then released back into the status bar area
final SurfaceControl sc = change.getLeash();
@@ -210,13 +245,27 @@
.setWindowCrop(sc, endBounds.width(), endBounds.height())
.apply();
+ if (mMoveToDesktopAnimator == null
+ || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+ Slog.e(TAG, "No animator available for this transition");
+ return false;
+ }
+
+ // End the animation that shrinks the window when task is first dragged from fullscreen
+ mMoveToDesktopAnimator.endAnimator();
+
final ValueAnimator animator = new ValueAnimator();
- animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f);
+ animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f);
animator.setDuration(FREEFORM_ANIMATION_DURATION);
final SurfaceControl.Transaction t = mTransactionSupplier.get();
+
+ // Get position of the task
+ final float x = mMoveToDesktopAnimator.getPosition().x;
+ final float y = mMoveToDesktopAnimator.getPosition().y;
+
animator.addUpdateListener(animation -> {
final float scale = (float) animation.getAnimatedValue();
- t.setPosition(sc, mPosition.x * (1 - scale), mPosition.y * (1 - scale))
+ t.setPosition(sc, x * (1 - scale), y * (1 - scale))
.setScale(sc, scale, scale)
.show(sc)
.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index d6451d7..73eb62a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -339,19 +339,36 @@
}
// This means an expand happened before enter-pip finished and we are now "merging" a
// no-op transition that happens to match our exit-pip.
+ // Or that the keyguard is up and preventing the transition from applying, in which case we
+ // want to manually reset pip. (b/283783868)
boolean cancelled = false;
if (mPipAnimationController.getCurrentAnimator() != null) {
mPipAnimationController.getCurrentAnimator().cancel();
+ mPipAnimationController.resetAnimatorState();
cancelled = true;
}
+
// Unset exitTransition AFTER cancel so that finishResize knows we are merging.
mExitTransition = null;
- if (!cancelled || aborted) return;
+ if (!cancelled) return;
final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo();
if (taskInfo != null) {
- startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(),
- mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
- new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */);
+ if (aborted) {
+ // keyguard case - the transition got aborted, so we want to reset state and
+ // windowing mode before reapplying the resize transaction
+ sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP);
+ mPipOrganizer.onExitPipFinished(taskInfo);
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP);
+ wct.setBounds(taskInfo.token, null);
+ mPipOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_LEAVE_PIP, false);
+ } else {
+ // merge case
+ startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(),
+ mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
+ new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */);
+ }
}
mExitDestinationBounds.setEmpty();
mCurrentPipTaskToken = null;
@@ -840,7 +857,7 @@
}
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
- final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ final Rect currentBounds = pipChange.getStartAbsBounds();
int rotationDelta = deltaRotation(startRotation, endRotation);
Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
@@ -1059,7 +1076,7 @@
// When the PIP window is visible and being a part of the transition, such as display
// rotation, we need to update its bounds and rounded corner.
final SurfaceControl leash = pipChange.getLeash();
- final Rect destBounds = mPipBoundsState.getBounds();
+ final Rect destBounds = mPipOrganizer.getCurrentOrAnimatingBounds();
final boolean isInPip = mPipTransitionState.isInPip();
mSurfaceTransactionHelper
.crop(startTransaction, leash, destBounds)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 837f118..39b6675 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -573,10 +573,12 @@
}
final boolean isRootTask = taskInfo != null
&& TransitionInfo.isIndependent(change, info);
+ final boolean isRecentsTask = mRecentsTask != null
+ && mRecentsTask.equals(change.getContainer());
hasTaskChange = hasTaskChange || isRootTask;
final boolean isLeafTask = leafTaskFilter.test(change);
if (TransitionUtil.isOpeningType(change.getMode())) {
- if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+ if (isRecentsTask) {
recentsOpening = change;
} else if (isRootTask || isLeafTask) {
if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
@@ -591,7 +593,7 @@
openingTaskIsLeafs.add(isLeafTask ? 1 : 0);
}
} else if (TransitionUtil.isClosingType(change.getMode())) {
- if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+ if (isRecentsTask) {
foundRecentsClosing = true;
} else if (isRootTask || isLeafTask) {
if (closingTasks == null) {
@@ -612,7 +614,7 @@
if (!TransitionUtil.isOrderOnly(change) && isLeafTask) {
hasChangingApp = true;
} else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME
- && !mRecentsTask.equals(change.getContainer())) {
+ && !isRecentsTask ) {
// Unless it is a 3p launcher. This means that the 3p launcher was already
// visible (eg. the "pausing" task is translucent over the 3p launcher).
// Treat it as if we are "re-opening" the 3p launcher.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index e294229..af8ef17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -31,6 +31,7 @@
import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
+import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -50,6 +51,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.Slog;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
@@ -551,6 +553,8 @@
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startShortcut",
+ "app package " + packageName + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
return;
@@ -580,6 +584,8 @@
taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startShortcutAndTaskWithLegacyTransition",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -612,12 +618,14 @@
taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startShortcutAndTask",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
}
- mStageCoordinator.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
- splitPosition, splitRatio, remoteTransition, instanceId);
+ mStageCoordinator.startShortcutAndTask(shortcutInfo, activityOptions.toBundle(), taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId);
}
/**
@@ -647,6 +655,8 @@
taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntentAndTaskWithLegacyTransition",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -677,6 +687,8 @@
taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntentAndTask",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -705,6 +717,8 @@
pendingIntent2 = null;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntentsWithLegacyTransition",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -734,6 +748,8 @@
pendingIntent2 = null;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntents",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
}
@@ -780,6 +796,8 @@
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
+ Log.w(TAG, splitFailureMessage("startIntent",
+ "app package " + packageName1 + " does not support multi-instance"));
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
Toast.LENGTH_SHORT).show();
return;
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 acc1c5e..693bf6c 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
@@ -28,6 +28,7 @@
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
@@ -41,6 +42,7 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
+import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -217,6 +219,8 @@
private boolean mIsDropEntering;
private boolean mIsExiting;
private boolean mIsRootTranslucent;
+ @VisibleForTesting
+ int mTopStageAfterFoldDismiss;
private DefaultMixedHandler mMixedHandler;
private final Toast mSplitUnsupportedToast;
@@ -473,6 +477,8 @@
if (isEnteringSplit && mSideStage.getChildCount() == 0) {
mMainExecutor.execute(() -> exitSplitScreen(
null /* childrenToTop */, EXIT_REASON_UNKNOWN));
+ Log.w(TAG, splitFailureMessage("startShortcut",
+ "side stage was not populated"));
mSplitUnsupportedToast.show();
}
@@ -560,6 +566,8 @@
if (isEnteringSplit && mSideStage.getChildCount() == 0) {
mMainExecutor.execute(() -> exitSplitScreen(
null /* childrenToTop */, EXIT_REASON_UNKNOWN));
+ Log.w(TAG, splitFailureMessage("startIntentLegacy",
+ "side stage was not populated"));
mSplitUnsupportedToast.show();
}
@@ -1090,6 +1098,8 @@
mMainExecutor.execute(() ->
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled",
+ "main or side stage was not populated."));
mSplitUnsupportedToast.show();
} else {
mSyncQueue.queue(evictWct);
@@ -1109,6 +1119,8 @@
if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished",
+ "main or side stage was not populated"));
mSplitUnsupportedToast.show();
return;
}
@@ -1293,20 +1305,30 @@
final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible;
final boolean oneStageVisible =
mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible;
- if (oneStageVisible) {
+ if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) {
// 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.
- if (!ENABLE_SHELL_TRANSITIONS) {
- final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
- exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
- } else {
- final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
+ exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+ }
+
+ // Dismiss split if the flag record any side of stages.
+ if (mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Need manually clear here due to this transition might be aborted due to keyguard
+ // on top and lead to no visible change.
+ clearSplitPairedInRecents(EXIT_REASON_DEVICE_FOLDED);
final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareExitSplitScreen(dismissTop, wct);
- mSplitTransitions.startDismissTransition(wct, this, dismissTop,
- EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+ prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
+ mSplitTransitions.startDismissTransition(wct, this,
+ mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
+ } else {
+ exitSplitScreen(
+ mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+ EXIT_REASON_DEVICE_FOLDED);
}
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
}
}
@@ -1344,15 +1366,8 @@
if (!mMainStage.isActive() || mIsExiting) return;
onSplitScreenExit();
+ clearSplitPairedInRecents(exitReason);
- mRecentTasks.ifPresent(recentTasks -> {
- // Notify recents if we are exiting in a way that breaks the pair, and disable further
- // updates to splits in the recents until we enter split again
- if (shouldBreakPairedTaskInRecents(exitReason) && mShouldUpdateRecents) {
- recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId());
- recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId());
- }
- });
mShouldUpdateRecents = false;
mIsDividerRemoteAnimating = false;
mSplitRequest = null;
@@ -1479,6 +1494,17 @@
}
}
+ private void clearSplitPairedInRecents(@ExitReason int exitReason) {
+ if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return;
+
+ mRecentTasks.ifPresent(recentTasks -> {
+ // Notify recents if we are exiting in a way that breaks the pair, and disable further
+ // updates to splits in the recents until we enter split again
+ mMainStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
+ mSideStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
+ });
+ }
+
/**
* Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
* an existing WindowContainerTransaction (rather than applying immediately). This is intended
@@ -1561,6 +1587,8 @@
// split bounds.
wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token,
SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
+ mSplitLayout.getInvisibleBounds(mTempRect1);
+ mSplitLayout.setTaskBounds(wct, mSideStage.mRootTaskInfo, mTempRect1);
}
wct.reorder(mRootTaskInfo.token, true);
setRootForceTranslucent(false, wct);
@@ -2204,7 +2232,11 @@
}
}
- void updateSurfaces(SurfaceControl.Transaction transaction) {
+ /**
+ * Update surfaces of the split screen layout based on the current state
+ * @param transaction to write the updates to
+ */
+ public void updateSurfaces(SurfaceControl.Transaction transaction) {
updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false);
mSplitLayout.update(transaction);
}
@@ -2223,26 +2255,18 @@
@VisibleForTesting
void onFoldedStateChanged(boolean folded) {
- int topStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
if (!folded) return;
- if (!mMainStage.isActive()) return;
+ if (!isSplitActive() || !isSplitScreenVisible()) return;
+ // To avoid split dismiss when user fold the device and unfold to use later, we only
+ // record the flag here and try to dismiss on wakeUp callback to ensure split dismiss
+ // when user interact on phone folded.
if (mMainStage.isFocused()) {
- topStageAfterFoldDismiss = STAGE_TYPE_MAIN;
+ mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
} else if (mSideStage.isFocused()) {
- topStageAfterFoldDismiss = STAGE_TYPE_SIDE;
- }
-
- if (ENABLE_SHELL_TRANSITIONS) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareExitSplitScreen(topStageAfterFoldDismiss, wct);
- mSplitTransitions.startDismissTransition(wct, this,
- topStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
- } else {
- exitSplitScreen(
- topStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
- EXIT_REASON_DEVICE_FOLDED);
+ mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
}
}
@@ -2376,6 +2400,15 @@
// so appends operations to exit split.
prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
}
+ } else if (type == TRANSIT_KEYGUARD_OCCLUDE && triggerTask.topActivity != null
+ && isSplitScreenVisible()) {
+ // Split include show when lock activity case, check the top activity under which
+ // stage and move it to the top.
+ int top = triggerTask.topActivity.equals(mMainStage.mRootTaskInfo.topActivity)
+ ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ prepareExitSplitScreen(top, out);
+ mSplitTransitions.setDismissTransition(transition, top,
+ EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
}
// When split in the background, it should be only opening/dismissing transition and
@@ -2704,12 +2737,13 @@
}
} else {
if (mainChild == null || sideChild == null) {
- Log.w(TAG, "Launched 2 tasks in split, but didn't receive"
- + " 2 tasks in transition. Possibly one of them failed to launch");
final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
(sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
mSplitTransitions.mPendingEnter.cancel(
(cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+ Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
+ "launched 2 tasks in split, but didn't receive "
+ + "2 tasks in transition. Possibly one of them failed to launch"));
mSplitUnsupportedToast.show();
return true;
}
@@ -2853,18 +2887,24 @@
}
}
+ final ArrayMap<Integer, SurfaceControl> dismissingTasks = new ArrayMap<>();
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null) continue;
+ if (getStageOfTask(taskInfo) != null
+ || getSplitItemPosition(change.getLastParent()) != SPLIT_POSITION_UNDEFINED) {
+ dismissingTasks.put(taskInfo.taskId, change.getLeash());
+ }
+ }
+
+
if (shouldBreakPairedTaskInRecents(dismissReason)) {
// Notify recents if we are exiting in a way that breaks the pair, and disable further
// updates to splits in the recents until we enter split again
mRecentTasks.ifPresent(recentTasks -> {
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo != null && (getStageOfTask(taskInfo) != null
- || getSplitItemPosition(change.getLastParent())
- != SPLIT_POSITION_UNDEFINED)) {
- recentTasks.removeSplitPair(taskInfo.taskId);
- }
+ for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) {
+ recentTasks.removeSplitPair(dismissingTasks.keyAt(i));
}
});
}
@@ -2882,6 +2922,10 @@
t.hide(toStage == STAGE_TYPE_MAIN ? mSideStage.mRootLeash : mMainStage.mRootLeash);
t.setPosition(toStage == STAGE_TYPE_MAIN
? mMainStage.mRootLeash : mSideStage.mRootLeash, 0, 0);
+ } else {
+ for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) {
+ finishT.hide(dismissingTasks.valueAt(i));
+ }
}
if (toStage == STAGE_TYPE_UNDEFINED) {
@@ -2891,7 +2935,7 @@
}
// Hide divider and dim layer on transition finished.
- setDividerVisibility(false, finishT);
+ setDividerVisibility(false, t);
finishT.hide(mMainStage.mDimLayer);
finishT.hide(mSideStage.mDimLayer);
}
@@ -3159,7 +3203,7 @@
}
@Override
- public void onNoLongerSupportMultiWindow() {
+ public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) {
if (mMainStage.isActive()) {
final boolean isMainStage = mMainStageListener == this;
if (!ENABLE_SHELL_TRANSITIONS) {
@@ -3174,6 +3218,9 @@
prepareExitSplitScreen(stageType, wct);
mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+ Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
+ "app package " + taskInfo.baseActivity.getPackageName()
+ + " does not support splitscreen, or is a controlled activity type"));
mSplitUnsupportedToast.show();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 3ef4f02..af7bf36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -55,6 +55,7 @@
import java.io.PrintWriter;
import java.util.Optional;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -81,7 +82,7 @@
void onRootTaskVanished();
- void onNoLongerSupportMultiWindow();
+ void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo);
}
private final Context mContext;
@@ -226,7 +227,7 @@
taskInfo.getWindowingMode())) {
// Leave split screen if the task no longer supports multi window or have
// uncontrolled task.
- mCallbacks.onNoLongerSupportMultiWindow();
+ mCallbacks.onNoLongerSupportMultiWindow(taskInfo);
return;
}
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
@@ -347,6 +348,13 @@
wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
}
+ void doForAllChildTasks(Consumer<Integer> consumer) {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
+ consumer.accept(taskInfo.taskId);
+ }
+ }
+
/** Collects all the current child tasks and prepares transaction to evict them to display. */
void evictAllChildren(WindowContainerTransaction wct) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index c964df1..c2f15f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.startingsurface;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.graphics.Color.WHITE;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -77,6 +78,13 @@
@NonNull Runnable clearWindowHandler) {
final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
final int taskId = runningTaskInfo.taskId;
+
+ // if we're in PIP we don't want to create the snapshot
+ if (runningTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "did not create taskSnapshot due to being in PIP");
+ return null;
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"create taskSnapshot surface for task: %d", taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 4faa929..0d77a2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -26,6 +26,7 @@
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.view.SurfaceControl;
@@ -69,8 +70,10 @@
private final Rect mTmpRect = new Rect();
private final Rect mTmpRootRect = new Rect();
private final int[] mTmpLocation = new int[2];
+ private final Rect mBoundsOnScreen = new Rect();
private final TaskViewTaskController mTaskViewTaskController;
private Region mObscuredTouchRegion;
+ private Insets mCaptionInsets;
public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
super(context, null, 0, 0, true /* disableBackgroundLayer */);
@@ -169,6 +172,25 @@
}
/**
+ * Sets a region of the task to inset to allow for a caption bar. Currently only top insets
+ * are supported.
+ * <p>
+ * This region will be factored in as an area of taskview that is not touchable activity
+ * content (i.e. you don't need to additionally set {@link #setObscuredTouchRect(Rect)} for
+ * the caption area).
+ *
+ * @param captionInsets the insets to apply to task view.
+ */
+ public void setCaptionInsets(Insets captionInsets) {
+ mCaptionInsets = captionInsets;
+ if (captionInsets == null) {
+ // If captions are null we can set them now; otherwise they'll get set in
+ // onComputeInternalInsets.
+ mTaskViewTaskController.setCaptionInsets(null);
+ }
+ }
+
+ /**
* Call when view position or size has changed. Do not call when animating.
*/
public void onLocationChanged() {
@@ -230,6 +252,15 @@
getLocationInWindow(mTmpLocation);
mTmpRect.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight());
+ if (mCaptionInsets != null) {
+ mTmpRect.inset(mCaptionInsets);
+ getBoundsOnScreen(mBoundsOnScreen);
+ mTaskViewTaskController.setCaptionInsets(new Rect(
+ mBoundsOnScreen.left,
+ mBoundsOnScreen.top,
+ mBoundsOnScreen.right + getWidth(),
+ mBoundsOnScreen.top + mCaptionInsets.top));
+ }
inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE);
if (mObscuredTouchRegion != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 163cf50..064af04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -33,6 +33,7 @@
import android.util.CloseGuard;
import android.util.Slog;
import android.view.SurfaceControl;
+import android.view.WindowInsets;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -82,6 +83,10 @@
private TaskView.Listener mListener;
private Executor mListenerExecutor;
+ /** Used to inset the activity content to allow space for a caption bar. */
+ private final Binder mCaptionInsetsOwner = new Binder();
+ private Rect mCaptionInsets;
+
public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
mContext = context;
@@ -436,6 +441,32 @@
mTaskViewTransitions.closeTaskView(wct, this);
}
+ /**
+ * Sets a region of the task to inset to allow for a caption bar.
+ *
+ * @param captionInsets the rect for the insets in screen coordinates.
+ */
+ void setCaptionInsets(Rect captionInsets) {
+ if (mCaptionInsets != null && mCaptionInsets.equals(captionInsets)) {
+ return;
+ }
+ mCaptionInsets = captionInsets;
+ applyCaptionInsetsIfNeeded();
+ }
+
+ void applyCaptionInsetsIfNeeded() {
+ if (mTaskToken == null) return;
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (mCaptionInsets != null) {
+ wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
+ WindowInsets.Type.captionBar(), mCaptionInsets);
+ } else {
+ wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
+ WindowInsets.Type.captionBar());
+ }
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
/** Should be called when the client surface is destroyed. */
public void surfaceDestroyed() {
mSurfaceCreated = false;
@@ -564,6 +595,7 @@
mTaskViewTransitions.updateBoundsState(this, boundsOnScreen);
mTaskViewTransitions.updateVisibilityState(this, true /* visible */);
wct.setBounds(mTaskToken, boundsOnScreen);
+ applyCaptionInsetsIfNeeded();
} else {
// The surface has already been destroyed before the task has appeared,
// so go ahead and hide the task entirely
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 5baf2e3..16f0e39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -202,15 +202,10 @@
if (taskView == null) return null;
// Opening types should all be initiated by shell
if (!TransitionUtil.isClosingType(request.getType())) return null;
- PendingTransition pending = findPendingCloseTransition(taskView);
- if (pending == null) {
- pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */);
- }
- if (pending.mClaimed != null) {
- throw new IllegalStateException("Task is closing in 2 collecting transitions?"
- + " This state doesn't make sense");
- }
+ PendingTransition pending = new PendingTransition(request.getType(), null,
+ taskView, null /* cookie */);
pending.mClaimed = transition;
+ mPending.add(pending);
return new WindowContainerTransaction();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index a28ce55..c5c22de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -25,6 +25,7 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
@@ -500,6 +501,7 @@
}
}
+ mPipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
finishCB);
// Dispatch the rest of the transition normally. This will most-likely be taken by
@@ -702,6 +704,9 @@
if (mPipHandler != null) {
mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
}
+ if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
+ mSplitHandler.updateSurfaces(startTransaction);
+ }
return mUnfoldHandler.startAnimation(
mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index 21994a9..bb5d546 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -270,7 +270,6 @@
@Override
public void prepareStartTransaction(Transaction transaction) {
mUnfoldBackgroundController.ensureBackground(transaction);
- mSplitScreenController.get().get().updateSplitScreenSurfaces(transaction);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index b217bd3..ce81910 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -145,7 +145,9 @@
mDisplay.getDisplayId(),
0 /* taskCornerRadius */,
mDecorationContainerSurface,
- mDragPositioningCallback);
+ mDragPositioningCallback,
+ mSurfaceControlBuilderSupplier,
+ mSurfaceControlTransactionSupplier);
}
final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 7245bc9..14f2f9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -24,9 +24,9 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.DRAG_FREEFORM_SCALE;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
+import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -112,9 +112,8 @@
private SplitScreenController mSplitScreenController;
- private ValueAnimator mDragToDesktopValueAnimator;
+ private MoveToDesktopAnimator mMoveToDesktopAnimator;
private final Rect mDragToDesktopAnimationStartBounds = new Rect();
- private boolean mDragToDesktopAnimationStarted;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -233,7 +232,6 @@
removeTaskFromEventReceiver(oldTaskInfo.displayId);
incrementEventReceiverTasks(taskInfo.displayId);
}
-
decoration.relayout(taskInfo);
}
@@ -599,7 +597,7 @@
}
case MotionEvent.ACTION_UP: {
if (relevantDecor == null) {
- mDragToDesktopAnimationStarted = false;
+ mMoveToDesktopAnimator = null;
mTransitionDragActive = false;
return;
}
@@ -613,14 +611,14 @@
} else if (DesktopModeStatus.isProto1Enabled()) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
}
- mDragToDesktopAnimationStarted = false;
+ mMoveToDesktopAnimator = null;
return;
- } else if (mDragToDesktopAnimationStarted) {
- Point position = new Point((int) ev.getX(), (int) ev.getY());
+ } else if (mMoveToDesktopAnimator != null) {
relevantDecor.incrementRelayoutBlock();
mDesktopTasksController.ifPresent(
- c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, position));
- mDragToDesktopAnimationStarted = false;
+ c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
+ mMoveToDesktopAnimator));
+ mMoveToDesktopAnimator = null;
return;
}
}
@@ -640,21 +638,19 @@
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
if (ev.getY() > statusBarHeight) {
- if (!mDragToDesktopAnimationStarted) {
- mDragToDesktopAnimationStarted = true;
+ if (mMoveToDesktopAnimator == null) {
+ mMoveToDesktopAnimator = new MoveToDesktopAnimator(
+ mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo,
+ relevantDecor.mTaskSurface);
mDesktopTasksController.ifPresent(
c -> c.moveToFreeform(relevantDecor.mTaskInfo,
- mDragToDesktopAnimationStartBounds));
- startAnimation(relevantDecor);
+ mDragToDesktopAnimationStartBounds,
+ mMoveToDesktopAnimator));
+ mMoveToDesktopAnimator.startAnimation();
}
}
- if (mDragToDesktopAnimationStarted) {
- Transaction t = mTransactionFactory.get();
- float width = (float) mDragToDesktopValueAnimator.getAnimatedValue()
- * mDragToDesktopAnimationStartBounds.width();
- float x = ev.getX() - (width / 2);
- t.setPosition(relevantDecor.mTaskSurface, x, ev.getY());
- t.apply();
+ if (mMoveToDesktopAnimator != null) {
+ mMoveToDesktopAnimator.updatePosition(ev);
}
}
break;
@@ -662,7 +658,7 @@
case MotionEvent.ACTION_CANCEL: {
mTransitionDragActive = false;
- mDragToDesktopAnimationStarted = false;
+ mMoveToDesktopAnimator = null;
}
}
}
@@ -729,20 +725,6 @@
animator.start();
}
- private void startAnimation(@NonNull DesktopModeWindowDecoration focusedDecor) {
- mDragToDesktopValueAnimator = ValueAnimator.ofFloat(1f, DRAG_FREEFORM_SCALE);
- mDragToDesktopValueAnimator.setDuration(FREEFORM_ANIMATION_DURATION);
- final Transaction t = mTransactionFactory.get();
- mDragToDesktopValueAnimator.addUpdateListener(animation -> {
- final float animatorValue = (float) animation.getAnimatedValue();
- SurfaceControl sc = focusedDecor.mTaskSurface;
- t.setScale(sc, animatorValue, animatorValue);
- t.apply();
- });
-
- mDragToDesktopValueAnimator.start();
- }
-
@Nullable
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index bc89385..a359395 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -234,7 +234,9 @@
mDisplay.getDisplayId(),
mRelayoutParams.mCornerRadius,
mDecorationContainerSurface,
- mDragPositioningCallback);
+ mDragPositioningCallback,
+ mSurfaceControlBuilderSupplier,
+ mSurfaceControlTransactionSupplier);
}
final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index 4e98f0c..941617d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -22,7 +22,9 @@
* Callback called when receiving drag-resize or drag-move related input events.
*/
public interface DragPositioningCallback {
- @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
+ @IntDef(flag = true, value = {
+ CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM
+ })
@interface CtrlType {}
int CTRL_TYPE_UNDEFINED = 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index e5fc66a..7c6fb99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -18,8 +18,11 @@
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
@@ -48,6 +51,8 @@
import com.android.internal.view.BaseIWindow;
+import java.util.function.Supplier;
+
/**
* An input event listener registered to InputDispatcher to receive input events on task edges and
* and corners. Converts them to drag resize requests.
@@ -60,6 +65,7 @@
private final Handler mHandler;
private final Choreographer mChoreographer;
private final InputManager mInputManager;
+ private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
private final int mDisplayId;
private final BaseIWindow mFakeWindow;
@@ -69,6 +75,10 @@
private final TaskResizeInputEventReceiver mInputEventReceiver;
private final DragPositioningCallback mCallback;
+ private final SurfaceControl mInputSinkSurface;
+ private final BaseIWindow mFakeSinkWindow;
+ private final InputChannel mSinkInputChannel;
+
private int mTaskWidth;
private int mTaskHeight;
private int mResizeHandleThickness;
@@ -90,15 +100,18 @@
int displayId,
int taskCornerRadius,
SurfaceControl decorationSurface,
- DragPositioningCallback callback) {
+ DragPositioningCallback callback,
+ Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+ Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
mInputManager = context.getSystemService(InputManager.class);
mHandler = handler;
mChoreographer = choreographer;
+ mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mDisplayId = displayId;
mTaskCornerRadius = taskCornerRadius;
mDecorationSurface = decorationSurface;
- // Use a fake window as the backing surface is a container layer and we don't want to create
- // a buffer layer for it so we can't use ViewRootImpl.
+ // Use a fake window as the backing surface is a container layer, and we don't want to
+ // create a buffer layer for it, so we can't use ViewRootImpl.
mFakeWindow = new BaseIWindow();
mFakeWindow.setSession(mWindowSession);
mFocusGrantToken = new Binder();
@@ -111,7 +124,7 @@
null /* hostInputToken */,
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
- 0 /* inputFeatures */,
+ INPUT_FEATURE_SPY,
TYPE_APPLICATION,
null /* windowToken */,
mFocusGrantToken,
@@ -126,6 +139,35 @@
mCallback = callback;
mDragDetector = new DragDetector(mInputEventReceiver);
mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
+
+ mInputSinkSurface = surfaceControlBuilderSupplier.get()
+ .setName("TaskInputSink of " + decorationSurface)
+ .setContainerLayer()
+ .setParent(mDecorationSurface)
+ .build();
+ mSurfaceControlTransactionSupplier.get()
+ .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER)
+ .show(mInputSinkSurface)
+ .apply();
+ mFakeSinkWindow = new BaseIWindow();
+ mSinkInputChannel = new InputChannel();
+ try {
+ mWindowSession.grantInputChannel(
+ mDisplayId,
+ mInputSinkSurface,
+ mFakeSinkWindow,
+ null /* hostInputToken */,
+ FLAG_NOT_FOCUSABLE,
+ 0 /* privateFlags */,
+ INPUT_FEATURE_NO_INPUT_CHANNEL,
+ TYPE_INPUT_CONSUMER,
+ null /* windowToken */,
+ mFocusGrantToken,
+ "TaskInputSink of " + decorationSurface,
+ mSinkInputChannel);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
/**
@@ -219,7 +261,35 @@
mDecorationSurface,
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
- 0 /* inputFeatures */,
+ INPUT_FEATURE_SPY,
+ touchRegion);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ mSurfaceControlTransactionSupplier.get()
+ .setWindowCrop(mInputSinkSurface, mTaskWidth, mTaskHeight)
+ .apply();
+ // The touch region of the TaskInputSink should be the touch region of this
+ // DragResizeInputHandler minus the task bounds. Pilfering events isn't enough to prevent
+ // input windows from handling down events, which will bring tasks in the back to front.
+ //
+ // Note not the entire touch region responds to both mouse and touchscreen events.
+ // Therefore, in the region that only responds to one of them, it would be a no-op to
+ // perform a gesture in the other type of events. We currently only have a mouse-only region
+ // out of the task bounds, and due to the roughness of touchscreen events, it's not a severe
+ // issue. However, were there touchscreen-only a region out of the task bounds, mouse
+ // gestures will become no-op in that region, even though the mouse gestures may appear to
+ // be performed on the input window behind the resize handle.
+ touchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE);
+ try {
+ mWindowSession.updateInputChannel(
+ mSinkInputChannel.getToken(),
+ mDisplayId,
+ mInputSinkSurface,
+ FLAG_NOT_FOCUSABLE,
+ 0 /* privateFlags */,
+ INPUT_FEATURE_NO_INPUT_CHANNEL,
touchRegion);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -248,6 +318,16 @@
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
+
+ mSinkInputChannel.dispose();
+ try {
+ mWindowSession.remove(mFakeSinkWindow);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ mSurfaceControlTransactionSupplier.get()
+ .remove(mInputSinkSurface)
+ .apply();
}
private class TaskResizeInputEventReceiver extends InputEventReceiver
@@ -256,6 +336,7 @@
private final Runnable mConsumeBatchEventRunnable;
private boolean mConsumeBatchEventScheduled;
private boolean mShouldHandleEvents;
+ private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -316,6 +397,8 @@
mShouldHandleEvents = isInResizeHandleBounds(x, y);
}
if (mShouldHandleEvents) {
+ mInputManager.pilferPointers(mInputChannel.getToken());
+
mDragPointerId = e.getPointerId(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
@@ -355,7 +438,6 @@
break;
}
case MotionEvent.ACTION_HOVER_EXIT:
- mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT);
result = true;
break;
}
@@ -395,7 +477,13 @@
if (y > mTaskHeight - mTaskCornerRadius) {
ctrlType |= CTRL_TYPE_BOTTOM;
}
- return checkDistanceFromCenter(ctrlType, x, y);
+ // Check distances from the center if it's in one of four corners.
+ if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
+ && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
+ return checkDistanceFromCenter(ctrlType, x, y);
+ }
+ // Otherwise, we should make sure we don't resize tasks inside task bounds.
+ return (x < 0 || y < 0 || x >= mTaskWidth || y >= mTaskHeight) ? ctrlType : 0;
}
// If corner input is not within appropriate distance of corner radius, do not use it.
@@ -429,7 +517,8 @@
break;
}
default: {
- return ctrlType;
+ throw new IllegalArgumentException("ctrlType should be complex, but it's 0x"
+ + Integer.toHexString(ctrlType));
}
}
double distanceFromCenter = Math.hypot(x - centerX, y - centerY);
@@ -482,7 +571,19 @@
cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
break;
}
- mInputManager.setPointerIconType(cursorType);
+ // Only update the cursor type to default once so that views behind the decor container
+ // layer that aren't in the active resizing regions have chances to update the cursor
+ // type. We would like to enforce the cursor type by setting the cursor type multilple
+ // times in active regions because we shouldn't allow the views behind to change it, as
+ // we'll pilfer the gesture initiated in this area. This is necessary because 1) we
+ // should allow the views behind regions only for touches to set the cursor type; and 2)
+ // there is a small region out of each rounded corner that's inside the task bounds,
+ // where views in the task can receive input events because we can't set touch regions
+ // of input sinks to have rounded corners.
+ if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) {
+ mInputManager.setPointerIconType(cursorType);
+ mLastCursorType = cursorType;
+ }
}
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 917abf5..e1b6db5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -63,8 +63,6 @@
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
- mDisplayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
- .getStableBounds(mStableBounds);
}
@Override
@@ -80,6 +78,10 @@
mTaskOrganizer.applyTransaction(wct);
}
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+ if (mStableBounds.isEmpty()) {
+ mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
+ .getStableBounds(mStableBounds);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
new file mode 100644
index 0000000..b2267dd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
@@ -0,0 +1,72 @@
+package com.android.wm.shell.windowdecor
+
+import android.animation.ValueAnimator
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.PointF
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.SurfaceControl
+
+/**
+ * Creates an animator to shrink and position task after a user drags a fullscreen task from
+ * the top of the screen to transition it into freeform and before the user releases the task. The
+ * MoveToDesktopAnimator object also holds information about the state of the task that are
+ * accessed by the EnterDesktopTaskTransitionHandler.
+ */
+class MoveToDesktopAnimator @JvmOverloads constructor(
+ private val startBounds: Rect,
+ private val taskInfo: RunningTaskInfo,
+ private val taskSurface: SurfaceControl,
+ private val transactionFactory: () -> SurfaceControl.Transaction =
+ SurfaceControl::Transaction
+) {
+ companion object {
+ // The size of the screen during drag relative to the fullscreen size
+ const val DRAG_FREEFORM_SCALE: Float = 0.4f
+ const val ANIMATION_DURATION = 336
+ }
+
+ private val animatedTaskWidth
+ get() = dragToDesktopAnimator.animatedValue as Float * startBounds.width()
+ private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f,
+ DRAG_FREEFORM_SCALE)
+ .setDuration(ANIMATION_DURATION.toLong())
+ .apply {
+ val t = SurfaceControl.Transaction()
+ addUpdateListener { animation ->
+ val animatorValue = animation.animatedValue as Float
+ t.setScale(taskSurface, animatorValue, animatorValue)
+ .apply()
+ }
+ }
+
+ val taskId get() = taskInfo.taskId
+ val position: PointF = PointF(0.0f, 0.0f)
+
+ /**
+ * Starts the animation that scales the task down.
+ */
+ fun startAnimation() {
+ dragToDesktopAnimator.start()
+ }
+
+ /**
+ * Uses the position of the motion event and the current scale of the task as defined by the
+ * ValueAnimator to update the local position variable and set the task surface's position
+ */
+ fun updatePosition(ev: MotionEvent) {
+ position.x = ev.x - animatedTaskWidth / 2
+ position.y = ev.y
+
+ val t = transactionFactory()
+ t.setPosition(taskSurface, position.x, position.y)
+ t.apply()
+ }
+
+ /**
+ * Ends the animation, setting the scale and position to the final animation value
+ */
+ fun endAnimator() {
+ dragToDesktopAnimator.end()
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index bf3ff3f..ae3b5eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -80,8 +80,6 @@
mTransactionSupplier = supplier;
mTransitions = transitions;
mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
- mDisplayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
- .getStableBounds(mStableBounds);
}
@Override
@@ -100,6 +98,10 @@
}
mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+ if (mStableBounds.isEmpty()) {
+ mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId())
+ .getStableBounds(mStableBounds);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index ddc7fef..0b0d9d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -64,6 +64,24 @@
implements AutoCloseable {
/**
+ * The Z-order of {@link #mCaptionContainerSurface}.
+ * <p>
+ * We use {@link #mDecorationContainerSurface} to define input window for task resizing; by
+ * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input
+ * prior to caption view itself, treating corner inputs as resize events rather than
+ * repositioning.
+ */
+ static final int CAPTION_LAYER_Z_ORDER = -1;
+ /**
+ * The Z-order of the task input sink in {@link DragPositioningCallback}.
+ * <p>
+ * This task input sink is used to prevent undesired dispatching of motion events out of task
+ * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle
+ * input events first.
+ */
+ static final int INPUT_SINK_Z_ORDER = -2;
+
+ /**
* System-wide context. Only used to create context with overridden configurations.
*/
final Context mContext;
@@ -238,11 +256,8 @@
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = taskBounds.width();
- // We use mDecorationContainerSurface to define input window for task resizing; by layering
- // it in front of mCaptionContainerSurface, we can allow it to handle input prior to
- // caption view itself, treating corner inputs as resize events rather than repositioning.
startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
- .setLayer(mCaptionContainerSurface, -1)
+ .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
.show(mCaptionContainerSurface);
if (ViewRootImpl.CAPTION_ON_SHELL) {
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index e382a0f..208ae84 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -30,19 +30,26 @@
filegroup {
name: "WMShellFlickerTestsBubbles-src",
- srcs: ["src/**/bubble/*.kt"],
+ srcs: ["src/com/android/wm/shell/flicker/bubble/*.kt"],
}
filegroup {
name: "WMShellFlickerTestsPip-src",
- srcs: ["src/**/pip/*.kt"],
+ srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"],
}
filegroup {
name: "WMShellFlickerTestsSplitScreen-src",
srcs: [
- "src/**/splitscreen/*.kt",
- "src/**/splitscreen/benchmark/*.kt",
+ "src/com/android/wm/shell/flicker/splitscreen/*.kt",
+ "src/com/android/wm/shell/flicker/splitscreen/benchmark/*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerServiceTests-src",
+ srcs: [
+ "src/com/android/wm/shell/flicker/service/**/*.kt",
],
}
@@ -88,6 +95,7 @@
":WMShellFlickerTestsBubbles-src",
":WMShellFlickerTestsPip-src",
":WMShellFlickerTestsSplitScreen-src",
+ ":WMShellFlickerServiceTests-src",
],
}
@@ -126,3 +134,15 @@
":WMShellFlickerTestsSplitScreen-src",
],
}
+
+android_test {
+ name: "WMShellFlickerServiceTests",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestService.xml"],
+ package_name: "com.android.wm.shell.flicker.service",
+ instrumentation_target_package: "com.android.wm.shell.flicker.service",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerServiceTests-src",
+ ],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
index 991d7b5..c8a9637 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -93,6 +93,8 @@
value="/data/user/0/com.android.server.wm.flicker.pip/files"/>
<option name="directory-keys"
value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.server.wm.flicker.service/files"/>
<option name="collect-on-run-ended-only" value="true"/>
<option name="clean-up" value="true"/>
</metrics_collector>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml
new file mode 100644
index 0000000..c7aca1a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.wm.shell.flicker.service">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.wm.shell.flicker.service"
+ android:label="WindowManager Flicker Service Tests">
+ </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt
index f4828f1..fd56a6e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.splitscreen
+package com.android.wm.shell.flicker
import android.app.Instrumentation
import android.graphics.Point
@@ -40,8 +40,6 @@
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary
-import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
import org.junit.Assert.assertNotNull
internal object SplitScreenUtils {
@@ -114,13 +112,12 @@
}
fun enterSplitViaIntent(
- wmHelper: WindowManagerStateHelper,
- primaryApp: StandardAppHelper,
- secondaryApp: StandardAppHelper
+ wmHelper: WindowManagerStateHelper,
+ primaryApp: StandardAppHelper,
+ secondaryApp: StandardAppHelper
) {
val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true")
- primaryApp.launchViaIntent(wmHelper, null, null,
- stringExtras)
+ primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
new file mode 100644
index 0000000..d3f3c5b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2023 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.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Rect
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switching to letterboxed app from launcher
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:QuickSwitchLauncherToLetterboxAppTest`
+ *
+ * Actions:
+ * ```
+ * Launch a letterboxed app
+ * Navigate home to show launcher
+ * Swipe right from the bottom of the screen to quick switch back to the app
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) :
+ BaseAppCompat(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit = {
+ setup {
+ tapl.setExpectedRotationCheckEnabled(false)
+
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+
+ letterboxApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ wmHelper
+ .StateSyncBuilder()
+ .withHomeActivityVisible()
+ .withWindowSurfaceDisappeared(letterboxApp)
+ .waitForAndVerify()
+
+ startDisplayBounds =
+ wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+ }
+ transitions {
+ tapl.workspace.quickSwitchToPreviousApp()
+ wmHelper
+ .StateSyncBuilder()
+ .withFullScreenApp(letterboxApp)
+ .withNavOrTaskBarVisible()
+ .withStatusBarVisible()
+ .waitForAndVerify()
+ }
+ teardown { letterboxApp.exit(wmHelper) }
+ }
+
+ /**
+ * Checks that [letterboxApp] is the top window at the end of the transition once we have fully
+ * quick switched from the launcher back to the [letterboxApp].
+ */
+ @Postsubmit
+ @Test
+ fun endsWithAppBeingOnTop() {
+ flicker.assertWmEnd { this.isAppWindowOnTop(letterboxApp) }
+ }
+
+ /** Checks that the transition starts with the home activity being tagged as visible. */
+ @Postsubmit
+ @Test
+ fun startsWithHomeActivityFlaggedVisible() {
+ flicker.assertWmStart { this.isHomeActivityVisible() }
+ }
+
+ /**
+ * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] windows
+ * filling/covering exactly display size
+ */
+ @Postsubmit
+ @Test
+ fun startsWithLauncherWindowsCoverFullScreen() {
+ flicker.assertWmStart {
+ this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] layers
+ * filling/covering exactly the display size.
+ */
+ @Postsubmit
+ @Test
+ fun startsWithLauncherLayersCoverFullScreen() {
+ flicker.assertLayersStart {
+ this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] being the top
+ * window.
+ */
+ @Postsubmit
+ @Test
+ fun startsWithLauncherBeingOnTop() {
+ flicker.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) }
+ }
+
+ /**
+ * Checks that the transition ends with the home activity being flagged as not visible. By this
+ * point we should have quick switched away from the launcher back to the [letterboxApp].
+ */
+ @Postsubmit
+ @Test
+ fun endsWithHomeActivityFlaggedInvisible() {
+ flicker.assertWmEnd { this.isHomeActivityInvisible() }
+ }
+
+ /**
+ * Checks that [letterboxApp]'s window starts off invisible and becomes visible at some point
+ * before the end of the transition and then stays visible until the end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun appWindowBecomesAndStaysVisible() {
+ flicker.assertWm {
+ this.isAppWindowInvisible(letterboxApp).then().isAppWindowVisible(letterboxApp)
+ }
+ }
+
+ /**
+ * Checks that [letterboxApp]'s layer starts off invisible and becomes visible at some point
+ * before the end of the transition and then stays visible until the end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun appLayerBecomesAndStaysVisible() {
+ flicker.assertLayers { this.isInvisible(letterboxApp).then().isVisible(letterboxApp) }
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.LAUNCHER] window starts off visible and becomes
+ * invisible at some point before the end of the transition and then stays invisible until the
+ * end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun launcherWindowBecomesAndStaysInvisible() {
+ flicker.assertWm {
+ this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+ .then()
+ .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER)
+ }
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.LAUNCHER] layer starts off visible and becomes
+ * invisible at some point before the end of the transition and then stays invisible until the
+ * end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun launcherLayerBecomesAndStaysInvisible() {
+ flicker.assertLayers {
+ this.isVisible(ComponentNameMatcher.LAUNCHER)
+ .then()
+ .isInvisible(ComponentNameMatcher.LAUNCHER)
+ }
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.LAUNCHER] window is visible at least until the app
+ * window is visible. Ensures that at any point, either the launcher or [letterboxApp] windows
+ * are at least partially visible.
+ */
+ @Postsubmit
+ @Test
+ fun appWindowIsVisibleOnceLauncherWindowIsInvisible() {
+ flicker.assertWm {
+ this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+ .then()
+ .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+ .then()
+ .isAppWindowVisible(letterboxApp)
+ }
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.LAUNCHER] layer is visible at least until the app layer
+ * is visible. Ensures that at any point, either the launcher or [letterboxApp] layers are at
+ * least partially visible.
+ */
+ @Postsubmit
+ @Test
+ fun appLayerIsVisibleOnceLauncherLayerIsInvisible() {
+ flicker.assertLayers {
+ this.isVisible(ComponentNameMatcher.LAUNCHER)
+ .then()
+ .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+ .then()
+ .isVisible(letterboxApp)
+ }
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.LETTERBOX] layer is visible as soon as the
+ * [letterboxApp] layer is visible at the end of the transition once we have fully quick
+ * switched from the launcher back to the [letterboxApp].
+ */
+ @Postsubmit
+ @Test
+ fun appAndLetterboxLayersBothVisibleOnceLauncherIsInvisible() {
+ flicker.assertLayers {
+ this.isVisible(ComponentNameMatcher.LAUNCHER)
+ .then()
+ .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+ .then()
+ .isVisible(letterboxApp)
+ .isVisible(ComponentNameMatcher.LETTERBOX)
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ companion object {
+ /** {@inheritDoc} */
+ private var startDisplayBounds = Rect.EMPTY
+
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return LegacyFlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
+ supportedRotations = listOf(Rotation.ROTATION_90)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
index 36bbafb..8bd44c3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -16,12 +16,9 @@
package com.android.wm.shell.flicker.pip
-import android.app.Instrumentation
-import android.os.SystemClock
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.common.Rotation
-import android.tools.device.apphelpers.StandardAppHelper
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
@@ -29,13 +26,9 @@
import android.tools.device.helpers.WindowUtils
import android.tools.device.traces.parsers.toFlickerComponent
import androidx.test.filters.RequiresDevice
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.UiObject2
-import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.SplitScreenUtils
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -73,8 +66,7 @@
AutoEnterPipOnGoToHomeTest(flicker) {
private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
/** Second app used to enter split screen mode */
- protected val secondAppForSplitScreen = getSplitScreenApp(instrumentation)
- fun getSplitScreenApp(instrumentation: Instrumentation): StandardAppHelper =
+ private val secondAppForSplitScreen =
SimpleAppHelper(
instrumentation,
ActivityOptions.SplitScreen.Primary.LABEL,
@@ -88,14 +80,7 @@
secondAppForSplitScreen.launchViaIntent(wmHelper)
pipApp.launchViaIntent(wmHelper)
tapl.goHome()
- enterSplitScreen()
- // wait until split screen is established
- wmHelper
- .StateSyncBuilder()
- .withWindowSurfaceAppeared(pipApp)
- .withWindowSurfaceAppeared(secondAppForSplitScreen)
- .withSplitDividerVisible()
- .waitForAndVerify()
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, pipApp, secondAppForSplitScreen)
pipApp.enableAutoEnterForPipActivity()
}
teardown {
@@ -107,46 +92,6 @@
transitions { tapl.goHome() }
}
- // TODO(b/285400227) merge the code in a common utility - this is copied from SplitScreenUtils
- private val TIMEOUT_MS = 3_000L
- private val overviewSnapshotSelector: BySelector
- get() = By.res(LAUNCHER_UI_PACKAGE_NAME, "snapshot")
- private fun enterSplitScreen() {
- // Note: The initial split position in landscape is different between tablet and phone.
- // In landscape, tablet will let the first app split to right side, and phone will
- // split to left side.
- if (tapl.isTablet) {
- // TAPL's currentTask on tablet is sometimes not what we expected if the overview
- // contains more than 3 task views. We need to use uiautomator directly to find the
- // second task to split.
- tapl.workspace.switchToOverview().overviewActions.clickSplit()
- val snapshots =
- tapl.device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
- if (snapshots == null || snapshots.size < 1) {
- error("Fail to find a overview snapshot to split.")
- }
-
- // Find the second task in the upper right corner in split select mode by sorting
- // 'left' in descending order and 'top' in ascending order.
- snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
- t2.getVisibleBounds().left - t1.getVisibleBounds().left
- }
- snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
- t1.getVisibleBounds().top - t2.getVisibleBounds().top
- }
- snapshots[0].click()
- } else {
- tapl.workspace
- .switchToOverview()
- .currentTask
- .tapMenu()
- .tapSplitMenuItem()
- .currentTask
- .open()
- }
- SystemClock.sleep(TIMEOUT_MS)
- }
-
@Presubmit
@Test
override fun pipOverlayLayerAppearThenDisappear() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
new file mode 100644
index 0000000..610cede
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 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.service
+
+import android.app.Instrumentation
+import android.platform.test.rule.NavigationModeRule
+import android.platform.test.rule.PressHomeRule
+import android.platform.test.rule.UnlockScreenRule
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.apphelpers.MessagingAppHelper
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.device.flicker.rules.LaunchAppRule
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.RuleChain
+
+object Utils {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ fun testSetupRule(navigationMode: NavBar, rotation: Rotation): RuleChain {
+ return RuleChain.outerRule(UnlockScreenRule())
+ .around(
+ NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ false)
+ )
+ .around(
+ LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false)
+ )
+ .around(RemoveAllTasksButHomeRule())
+ .around(
+ ChangeDisplayOrientationRule(
+ rotation,
+ resetOrientationAfterTest = false,
+ clearCacheAfterParsing = false
+ )
+ )
+ .around(PressHomeRule())
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt
similarity index 95%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt
index f4828f1..e640dc4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.splitscreen
+package com.android.wm.shell.flicker.service.splitscreen
import android.app.Instrumentation
import android.graphics.Point
@@ -39,12 +39,11 @@
import com.android.server.wm.flicker.helpers.NotificationAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary
import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
import org.junit.Assert.assertNotNull
-internal object SplitScreenUtils {
+object SplitScreenUtils {
private const val TIMEOUT_MS = 3_000L
private const val DRAG_DURATION_MS = 1_000L
private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
@@ -113,17 +112,6 @@
waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
- fun enterSplitViaIntent(
- wmHelper: WindowManagerStateHelper,
- primaryApp: StandardAppHelper,
- secondaryApp: StandardAppHelper
- ) {
- val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true")
- primaryApp.launchViaIntent(wmHelper, null, null,
- stringExtras)
- waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
- }
-
fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
// Note: The initial split position in landscape is different between tablet and phone.
// In landscape, tablet will let the first app split to right side, and phone will
@@ -159,6 +147,17 @@
SystemClock.sleep(TIMEOUT_MS)
}
+ fun enterSplitViaIntent(
+ wmHelper: WindowManagerStateHelper,
+ primaryApp: StandardAppHelper,
+ secondaryApp: StandardAppHelper
+ ) {
+ val stringExtras =
+ mapOf(ActivityOptions.SplitScreen.Primary.EXTRA_LAUNCH_ADJACENT to "true")
+ primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
fun dragFromNotificationToSplit(
instrumentation: Instrumentation,
device: UiDevice,
@@ -326,14 +325,14 @@
dividerBar.drag(
Point(
if (dragToRight) {
- displayBounds.right
+ displayBounds.width * 4 / 5
} else {
- displayBounds.left
+ displayBounds.width * 1 / 5
},
if (dragToBottom) {
- displayBounds.bottom
+ displayBounds.height * 4 / 5
} else {
- displayBounds.top
+ displayBounds.height * 1 / 5
}
)
)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..566adec
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+
+@RequiresDevice
+class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun copyContentInSplit() = super.copyContentInSplit()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..92b6227
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+
+@RequiresDevice
+class CopyContentInSplitGesturalNavPortraitBenchmark : CopyContentInSplit(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun copyContentInSplit() = super.copyContentInSplit()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..e6d56b5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByDividerGesturalNavLandscapeBenchmark :
+ DismissSplitScreenByDivider(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..6752c58
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByDividerGesturalNavPortraitBenchmark :
+ DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..7c9ab99
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..4b79571
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..0495079
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+
+@RequiresDevice
+class DragDividerToResizeGesturalNavLandscapeBenchmark : DragDividerToResize(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..71ef48b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+
+@RequiresDevice
+class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..c78729c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..30bce2f6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..b33ea7c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..07a86a5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..9a1d127
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..266e268
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..83fc30b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b2f1929
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..dae92dd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..732047b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..1de7efd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+
+@RequiresDevice
+class SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..1a046aa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+
+@RequiresDevice
+class SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..6e88f0e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..d26a29c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..4a552b0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b7376ea
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..b2d05e4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..6de31b1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..aab18a6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark :
+ SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b074f2c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark :
+ SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..c402aa4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RequiresDevice
+@RunWith(BlockJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..840401c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RequiresDevice
+@RunWith(BlockJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark : UnlockKeyguardToSplitScreen() {
+ @PlatinumTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
new file mode 100644
index 0000000..a5c5122
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
+ @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
new file mode 100644
index 0000000..092fb67
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
+ @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
new file mode 100644
index 0000000..8cb25fe
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByDividerGesturalNavLandscape :
+ DismissSplitScreenByDivider(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
new file mode 100644
index 0000000..fa1be63
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByDividerGesturalNavPortrait :
+ DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..aa35237
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByGoHomeGesturalNavLandscape :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..e195360
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByGoHomeGesturalNavPortrait :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
new file mode 100644
index 0000000..c1b3aad
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
new file mode 100644
index 0000000..c6e2e85
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
new file mode 100644
index 0000000..5f771c7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
new file mode 100644
index 0000000..729a401
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
new file mode 100644
index 0000000..6e4cf9f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
new file mode 100644
index 0000000..cc28702
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
new file mode 100644
index 0000000..736604f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
new file mode 100644
index 0000000..8df8dfa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
new file mode 100644
index 0000000..378f055
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
new file mode 100644
index 0000000..b33d262
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
new file mode 100644
index 0000000..b1d3858
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenFromOverviewGesturalNavLandscape :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
new file mode 100644
index 0000000..6d824c7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenFromOverviewGesturalNavPortrait :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
new file mode 100644
index 0000000..f1d3d0c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchAppByDoubleTapDividerGesturalNavLandscape :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios([])
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
new file mode 100644
index 0000000..a867bac
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchAppByDoubleTapDividerGesturalNavPortrait :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios([])
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
new file mode 100644
index 0000000..76247ba
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
new file mode 100644
index 0000000..e179da8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..20f554f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromHomeGesturalNavLandscape :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..f7776ee
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromHomeGesturalNavPortrait :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
new file mode 100644
index 0000000..00f6073
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromRecentGesturalNavLandscape :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
new file mode 100644
index 0000000..b3340e7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromRecentGesturalNavPortrait :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
new file mode 100644
index 0000000..3da61e5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
new file mode 100644
index 0000000..627ae18
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
new file mode 100644
index 0000000..7cbc1c3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
new file mode 100644
index 0000000..2eb81e0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.flicker
+
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
new file mode 100644
index 0000000..5bfc889
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CopyContentInSplit
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+ private val textEditApp = SplitScreenUtils.getIme(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp)
+ }
+
+ @Test
+ open fun copyContentInSplit() {
+ SplitScreenUtils.copyContentInSplit(instrumentation, device, primaryApp, textEditApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
new file mode 100644
index 0000000..d07daff
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DismissSplitScreenByDivider
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ }
+
+ @Test
+ open fun dismissSplitScreenByDivider() {
+ if (tapl.isTablet) {
+ SplitScreenUtils.dragDividerToDismissSplit(
+ device,
+ wmHelper,
+ dragToRight = false,
+ dragToBottom = true
+ )
+ } else {
+ SplitScreenUtils.dragDividerToDismissSplit(
+ device,
+ wmHelper,
+ dragToRight = true,
+ dragToBottom = true
+ )
+ }
+ wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
new file mode 100644
index 0000000..d428dea
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DismissSplitScreenByGoHome
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ }
+
+ @Test
+ open fun dismissSplitScreenByGoHome() {
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
new file mode 100644
index 0000000..dc2a6ac
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DragDividerToResize
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ }
+
+ @Test
+ open fun dragDividerToResize() {
+ SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
new file mode 100644
index 0000000..677aeb07
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromAllApps
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ tapl.goHome()
+ primaryApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun enterSplitScreenByDragFromAllApps() {
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
new file mode 100644
index 0000000..f4f6878
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromNotification
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+ private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ // Send a notification
+ sendNotificationApp.launchViaIntent(wmHelper)
+ sendNotificationApp.postNotification(wmHelper)
+ tapl.goHome()
+ primaryApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun enterSplitScreenByDragFromNotification() {
+ SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ sendNotificationApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
new file mode 100644
index 0000000..36a458f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromShortcut
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(tapl.isTablet)
+
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ tapl.goHome()
+ SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+ primaryApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun enterSplitScreenByDragFromShortcut() {
+ tapl.launchedAppState.taskbar
+ .getAppIcon(secondaryApp.appName)
+ .openDeepShortcutMenu()
+ .getMenuItem("Split Screen Secondary Activity")
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+ // TODO: Do we want this check in here? Add to the other tests?
+ // flicker.splitScreenEntered(
+ // primaryApp,
+ // secondaryApp,
+ // fromOtherApp = false,
+ // appExistAtStart = false
+ // )
+ }
+
+ @After
+ fun teardwon() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
new file mode 100644
index 0000000..322f711
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromTaskbar
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ tapl.goHome()
+ SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+ primaryApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun enterSplitScreenByDragFromTaskbar() {
+ tapl.launchedAppState.taskbar
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
new file mode 100644
index 0000000..f164451
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenFromOverview
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ primaryApp.launchViaIntent(wmHelper)
+ secondaryApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+
+ @Test
+ open fun enterSplitScreenFromOverview() {
+ SplitScreenUtils.splitFromOverview(tapl, device)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt
similarity index 95%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt
index f4828f1..3831c65 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.splitscreen
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
import android.app.Instrumentation
import android.graphics.Point
@@ -39,12 +39,11 @@
import com.android.server.wm.flicker.helpers.NotificationAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary
import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
import org.junit.Assert.assertNotNull
-internal object SplitScreenUtils {
+object SplitScreenUtils {
private const val TIMEOUT_MS = 3_000L
private const val DRAG_DURATION_MS = 1_000L
private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
@@ -113,17 +112,6 @@
waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
- fun enterSplitViaIntent(
- wmHelper: WindowManagerStateHelper,
- primaryApp: StandardAppHelper,
- secondaryApp: StandardAppHelper
- ) {
- val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true")
- primaryApp.launchViaIntent(wmHelper, null, null,
- stringExtras)
- waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
- }
-
fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
// Note: The initial split position in landscape is different between tablet and phone.
// In landscape, tablet will let the first app split to right side, and phone will
@@ -159,6 +147,17 @@
SystemClock.sleep(TIMEOUT_MS)
}
+ fun enterSplitViaIntent(
+ wmHelper: WindowManagerStateHelper,
+ primaryApp: StandardAppHelper,
+ secondaryApp: StandardAppHelper
+ ) {
+ val stringExtras =
+ mapOf(ActivityOptions.SplitScreen.Primary.EXTRA_LAUNCH_ADJACENT to "true")
+ primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
fun dragFromNotificationToSplit(
instrumentation: Instrumentation,
device: UiDevice,
@@ -326,14 +325,14 @@
dividerBar.drag(
Point(
if (dragToRight) {
- displayBounds.right
+ displayBounds.width * 4 / 5
} else {
- displayBounds.left
+ displayBounds.width * 1 / 5
},
if (dragToBottom) {
- displayBounds.bottom
+ displayBounds.height * 4 / 5
} else {
- displayBounds.top
+ displayBounds.height * 1 / 5
}
)
)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
new file mode 100644
index 0000000..805d987
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.WindowUtils
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchAppByDoubleTapDivider
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ tapl.workspace.switchToOverview().dismissAllTasks()
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ }
+
+ @Test
+ open fun switchAppByDoubleTapDivider() {
+ SplitScreenUtils.doubleTapDividerToSwitch(device)
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+ waitForLayersToSwitch(wmHelper)
+ waitForWindowsToSwitch(wmHelper)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+
+ private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
+ wmHelper
+ .StateSyncBuilder()
+ .add("appWindowsSwitched") {
+ val primaryAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ primaryApp.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val secondaryAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ secondaryApp.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
+
+ if (isLandscape(rotation)) {
+ return@add if (isTablet()) {
+ secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+ } else {
+ primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+ }
+ } else {
+ return@add if (isTablet()) {
+ primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ } else {
+ primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ }
+ }
+ }
+ .waitForAndVerify()
+ }
+
+ private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
+ wmHelper
+ .StateSyncBuilder()
+ .add("appLayersSwitched") {
+ val primaryAppLayer =
+ it.layerState.visibleLayers.firstOrNull { window ->
+ primaryApp.layerMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val secondaryAppLayer =
+ it.layerState.visibleLayers.firstOrNull { window ->
+ secondaryApp.layerMatchesAnyOf(window)
+ }
+ ?: return@add false
+
+ val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
+ val secondaryVisibleRegion =
+ secondaryAppLayer.visibleRegion?.bounds ?: return@add false
+
+ if (isLandscape(rotation)) {
+ return@add if (isTablet()) {
+ secondaryVisibleRegion.right <= primaryVisibleRegion.left
+ } else {
+ primaryVisibleRegion.right <= secondaryVisibleRegion.left
+ }
+ } else {
+ return@add if (isTablet()) {
+ primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ } else {
+ primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ }
+ }
+ }
+ .waitForAndVerify()
+ }
+
+ private fun isLandscape(rotation: Rotation): Boolean {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ return displayBounds.width > displayBounds.height
+ }
+
+ private fun isTablet(): Boolean {
+ val sizeDp: Point = device.displaySizeDp
+ val LARGE_SCREEN_DP_THRESHOLD = 600
+ return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
new file mode 100644
index 0000000..4229ebb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromAnotherApp
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+ private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+ thirdApp.launchViaIntent(wmHelper)
+ wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
+ }
+
+ @Test
+ open fun switchBackToSplitFromAnotherApp() {
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
new file mode 100644
index 0000000..f2d56b9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromHome
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+
+ @Test
+ open fun switchBackToSplitFromHome() {
+ tapl.workspace.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
new file mode 100644
index 0000000..d44d177
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromRecent
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+
+ @Test
+ open fun switchBackToSplitFromRecent() {
+ tapl.workspace.switchToOverview().currentTask.open()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
new file mode 100644
index 0000000..e2c6ca6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBetweenSplitPairs
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+ private val thirdApp = SplitScreenUtils.getIme(instrumentation)
+ private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
+ }
+
+ @Test
+ open fun switchBetweenSplitPairs() {
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ thirdApp.exit(wmHelper)
+ fourthApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
new file mode 100644
index 0000000..df98d8f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class UnlockKeyguardToSplitScreen {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(Rotation.ROTATION_0.value)
+
+ SplitScreenUtils.enterSplitViaIntent(wmHelper, primaryApp, secondaryApp)
+ }
+
+ @Test
+ open fun unlockKeyguardToSplitScreen() {
+ device.sleep()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ device.wakeUp()
+ device.pressMenu()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index a43ad9b..1d4c4d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.common.traces.component.EdgeExtensionComponentMatcher
@@ -28,13 +27,9 @@
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.appWindowKeepVisible
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
@@ -60,21 +55,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(primaryApp)
- flicker.appWindowIsVisibleAtStart(textEditApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(textEditApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
-
- // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
- }
-
@Presubmit
@Test
fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index a118c08..0d967eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
@@ -26,13 +25,9 @@
import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.appWindowKeepVisible
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
@@ -58,19 +53,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(primaryApp)
- flicker.appWindowIsVisibleAtStart(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
- }
-
@Presubmit
@Test
fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index 580b153..d3434a5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -21,6 +21,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.BaseBenchmarkTest
+import com.android.wm.shell.flicker.SplitScreenUtils
abstract class SplitScreenBase(flicker: LegacyFlickerTest) : BaseBenchmarkTest(flicker) {
protected val context: Context = instrumentation.context
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index e0a47b3..f236c2d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.splitscreen
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
@@ -28,12 +27,9 @@
import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
@@ -59,19 +55,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(primaryApp)
- flicker.appWindowIsVisibleAtStart(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(secondaryApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
- }
-
@Presubmit
@Test
fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index 8f867df..8aaa98a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
@@ -28,15 +27,10 @@
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.layerBecomesInvisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
@@ -62,21 +56,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun cujCompleted() {
- flicker.appWindowIsVisibleAtStart(thirdApp)
- flicker.appWindowIsVisibleAtStart(fourthApp)
- flicker.splitScreenDividerIsVisibleAtStart()
-
- flicker.appWindowIsVisibleAtEnd(primaryApp)
- flicker.appWindowIsVisibleAtEnd(secondaryApp)
- flicker.appWindowIsInvisibleAtEnd(thirdApp)
- flicker.appWindowIsInvisibleAtEnd(fourthApp)
- flicker.splitScreenDividerIsVisibleAtEnd()
- }
-
@Presubmit
@Test
fun splitScreenDividerInvisibleAtMiddle() =
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
index d1ca9ea..d9d22de 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -16,18 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val textEditApp = SplitScreenUtils.getIme(instrumentation)
protected val magnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
@@ -54,21 +51,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- open fun cujCompleted() {
- // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
index 73acb1f..7e8d60b4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -16,18 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenDismissed
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -61,19 +57,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
index 86ffd2a..770e032 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -16,18 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenDismissed
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -47,19 +43,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
index dfde3b6..46570fd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -16,19 +16,16 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +34,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -45,27 +42,11 @@
transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- open fun cujCompleted() {
- // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
- // robust enough to get the correct end state.
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
index d13e413..5c3d4ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
@@ -57,30 +53,11 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
index 1d41669..6b122c6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromNotificationBenchmark(
+abstract class EnterSplitScreenByDragFromNotificationBenchmark(
override val flicker: LegacyFlickerTest
) : SplitScreenBase(flicker) {
protected val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -59,21 +55,6 @@
teardown { sendNotificationApp.exit(wmHelper) }
}
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
index b4bafa7..78f9bab 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,8 +35,9 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromShortcutBenchmark(override val flicker: LegacyFlickerTest) :
- SplitScreenBase(flicker) {
+abstract class EnterSplitScreenByDragFromShortcutBenchmark(
+ override val flicker: LegacyFlickerTest
+) : SplitScreenBase(flicker) {
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
@@ -62,25 +59,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
index da44ecd..78907f0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -16,21 +16,17 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -56,26 +52,6 @@
}
}
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
@Before
fun before() {
Assume.assumeTrue(tapl.isTablet)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
index af06d6d..2c91e84 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -16,18 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -56,19 +52,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index 23156b5..fa09c2e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -16,8 +16,6 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -27,10 +25,9 @@
import android.tools.device.helpers.WindowUtils
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,7 +36,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -53,14 +50,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
wmHelper
.StateSyncBuilder()
@@ -134,14 +123,6 @@
return displayBounds.width > displayBounds.height
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- open fun cujCompleted() {
- // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
- // robust enough to get the correct end state.
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
index 2d810d3..ff22006 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -16,19 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
@@ -55,19 +51,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
index f6df1e4..5787b02 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -16,19 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -53,19 +49,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
index ba46bdc..b2d5091 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -16,19 +16,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -53,19 +49,6 @@
}
}
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- withoutTracing(this)
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
index 0d871e5..f234e46 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -16,17 +16,14 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -35,7 +32,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thirdApp = SplitScreenUtils.getIme(instrumentation)
protected val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -64,9 +61,6 @@
thisTransition(this)
}
- @PlatinumTest(focusArea = "sysui")
- @Presubmit @Test open fun cujCompleted() {}
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
index 7952b71..61c3679 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
@@ -22,8 +22,8 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.SplitScreenUtils
import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -33,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) :
+abstract class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
@@ -47,14 +47,6 @@
}
}
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- defaultSetup(this)
- defaultTeardown(this)
- thisTransition(this)
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
index 8592dea..c6642f3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -28,6 +28,7 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -41,6 +42,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
import junit.framework.AssertionFailedError;
@@ -73,6 +75,10 @@
ShellExecutor mExecutor;
@Mock
SurfaceControl mSurfaceControl;
+ @Mock
+ MoveToDesktopAnimator mMoveToDesktopAnimator;
+ @Mock
+ PointF mPosition;
private EnterDesktopTaskTransitionHandler mEnterDesktopTaskTransitionHandler;
@@ -82,6 +88,7 @@
doReturn(mExecutor).when(mTransitions).getMainExecutor();
doReturn(mAnimationT).when(mTransactionFactory).get();
+ doReturn(mPosition).when(mMoveToDesktopAnimator).getPosition();
mEnterDesktopTaskTransitionHandler = new EnterDesktopTaskTransitionHandler(mTransitions,
mTransactionFactory);
@@ -89,12 +96,15 @@
@Test
public void testEnterFreeformAnimation() {
- final int transitionType = Transitions.TRANSIT_ENTER_FREEFORM;
final int taskId = 1;
WindowContainerTransaction wct = new WindowContainerTransaction();
doReturn(mToken).when(mTransitions)
- .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
- mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
+ .startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct,
+ mEnterDesktopTaskTransitionHandler);
+ doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId();
+
+ mEnterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct,
+ mMoveToDesktopAnimator, null);
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
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 e59d09c..cc4db22 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
@@ -347,13 +347,20 @@
}
@Test
- public void testExitSplitScreenAfterFolded() {
- when(mMainStage.isActive()).thenReturn(true);
+ public void testExitSplitScreenAfterFoldedAndWakeUp() {
when(mMainStage.isFocused()).thenReturn(true);
when(mMainStage.getTopVisibleChildTaskId()).thenReturn(INVALID_TASK_ID);
+ mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build();
+ mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build();
+ when(mStageCoordinator.isSplitActive()).thenReturn(true);
+ when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
mStageCoordinator.onFoldedStateChanged(true);
+ assertEquals(mStageCoordinator.mTopStageAfterFoldDismiss, STAGE_TYPE_MAIN);
+
+ mStageCoordinator.onFinishedWakingUp();
+
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
verify(mTaskOrganizer).startNewTransition(eq(TRANSIT_SPLIT_DISMISS), notNull());
} else {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index df1e2e1..946a7ef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -161,7 +161,7 @@
childTask.supportsMultiWindow = false;
mStageTaskListener.onTaskInfoChanged(childTask);
- verify(mCallbacks).onNoLongerSupportMultiWindow();
+ verify(mCallbacks).onNoLongerSupportMultiWindow(childTask);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 1b38956..50435a0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -40,6 +40,7 @@
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.Context;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.testing.AndroidTestingRunner;
@@ -563,4 +564,47 @@
mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
verify(mTaskViewTaskController, never()).cleanUpPendingTask();
}
+
+ @Test
+ public void testSetCaptionInsets_noTaskInitially() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ Rect insets = new Rect(0, 400, 0, 0);
+ mTaskView.setCaptionInsets(Insets.of(insets));
+ mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+
+ verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+ verify(mOrganizer, never()).applyTransaction(any());
+
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ reset(mOrganizer);
+ reset(mTaskViewTaskController);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
+ mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+
+ verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+ verify(mOrganizer).applyTransaction(any());
+ }
+
+ @Test
+ public void testSetCaptionInsets_withTask() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
+ reset(mTaskViewTaskController);
+ reset(mOrganizer);
+
+ Rect insets = new Rect(0, 400, 0, 0);
+ mTaskView.setCaptionInsets(Insets.of(insets));
+ mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+ verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+ verify(mOrganizer).applyTransaction(any());
+ }
}
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 4c9a23d..740988f 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -90,8 +90,8 @@
requireUnpremul, prefColorSpace);
}
- bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, const SkIRect& desiredSubset,
- int sampleSize, bool requireUnpremul) {
+ bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, int outWidth, int outHeight,
+ const SkIRect& desiredSubset, int sampleSize, bool requireUnpremul) {
SkColorType decodeColorType = mGainmapBRD->computeOutputColorType(kN32_SkColorType);
sk_sp<SkColorSpace> decodeColorSpace =
mGainmapBRD->computeOutputColorSpace(decodeColorType, nullptr);
@@ -109,9 +109,8 @@
// kPremul_SkAlphaType is used just as a placeholder as it doesn't change the underlying
// allocation type. RecyclingClippingPixelAllocator will populate this with the
// actual alpha type in either allocPixelRef() or copyIfNecessary()
- sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(
- SkImageInfo::Make(desiredSubset.width(), desiredSubset.height(), decodeColorType,
- kPremul_SkAlphaType, decodeColorSpace));
+ sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
+ outWidth, outHeight, decodeColorType, kPremul_SkAlphaType, decodeColorSpace));
if (!nativeBitmap) {
ALOGE("OOM allocating Bitmap for Gainmap");
return false;
@@ -134,9 +133,12 @@
return true;
}
- SkIRect calculateGainmapRegion(const SkIRect& mainImageRegion) {
+ SkIRect calculateGainmapRegion(const SkIRect& mainImageRegion, int* inOutWidth,
+ int* inOutHeight) {
const float scaleX = ((float)mGainmapBRD->width()) / mMainImageBRD->width();
const float scaleY = ((float)mGainmapBRD->height()) / mMainImageBRD->height();
+ *inOutWidth *= scaleX;
+ *inOutHeight *= scaleY;
// TODO: Account for rounding error?
return SkIRect::MakeLTRB(mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY,
mainImageRegion.right() * scaleX,
@@ -328,21 +330,16 @@
sp<uirenderer::Gainmap> gainmap;
bool hasGainmap = brd->hasGainmap();
if (hasGainmap) {
- SkIRect adjustedSubset{};
+ int gainmapWidth = bitmap.width();
+ int gainmapHeight = bitmap.height();
if (javaBitmap) {
- // Clamp to the width/height of the recycled bitmap in case the reused bitmap
- // was too small for the specified rectangle, in which case we need to clip
- adjustedSubset = SkIRect::MakeXYWH(inputX, inputY,
- std::min(subset.width(), recycledBitmap->width()),
- std::min(subset.height(), recycledBitmap->height()));
- } else {
- // We are not recycling, so use the decoded width/height for calculating the gainmap
- // subset instead to ensure the gainmap region proportionally matches
- adjustedSubset = SkIRect::MakeXYWH(std::max(0, inputX), std::max(0, inputY),
- bitmap.width(), bitmap.height());
+ // If we are recycling we must match the inBitmap's relative dimensions
+ gainmapWidth = recycledBitmap->width();
+ gainmapHeight = recycledBitmap->height();
}
- SkIRect gainmapSubset = brd->calculateGainmapRegion(adjustedSubset);
- if (!brd->decodeGainmapRegion(&gainmap, gainmapSubset, sampleSize, requireUnpremul)) {
+ SkIRect gainmapSubset = brd->calculateGainmapRegion(subset, &gainmapWidth, &gainmapHeight);
+ if (!brd->decodeGainmapRegion(&gainmap, gainmapWidth, gainmapHeight, gainmapSubset,
+ sampleSize, requireUnpremul)) {
// If there is an error decoding Gainmap - we don't fail. We just don't provide Gainmap
hasGainmap = false;
}
diff --git a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
index 2231ce1..e46d34e 100644
--- a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
+++ b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
@@ -17,9 +17,22 @@
package android.media.projection;
import android.media.projection.MediaProjectionInfo;
+import android.view.ContentRecordingSession;
/** {@hide} */
oneway interface IMediaProjectionWatcherCallback {
void onStart(in MediaProjectionInfo info);
void onStop(in MediaProjectionInfo info);
+ /**
+ * Called when the {@link ContentRecordingSession} was set for the current media
+ * projection.
+ *
+ * @param info always present and contains information about the media projection host.
+ * @param session the recording session for the current media projection. Can be
+ * {@code null} when the recording will stop.
+ */
+ void onRecordingSessionSet(
+ in MediaProjectionInfo info,
+ in @nullable ContentRecordingSession session
+ );
}
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 5703c42..5a68c53 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -29,6 +29,7 @@
import android.os.ServiceManager;
import android.util.ArrayMap;
import android.util.Log;
+import android.view.ContentRecordingSession;
import android.view.Surface;
import java.util.Map;
@@ -300,7 +301,22 @@
/** @hide */
public static abstract class Callback {
public abstract void onStart(MediaProjectionInfo info);
+
public abstract void onStop(MediaProjectionInfo info);
+
+ /**
+ * Called when the {@link ContentRecordingSession} was set for the current media
+ * projection.
+ *
+ * @param info always present and contains information about the media projection host.
+ * @param session the recording session for the current media projection. Can be
+ * {@code null} when the recording will stop.
+ */
+ public void onRecordingSessionSet(
+ @NonNull MediaProjectionInfo info,
+ @Nullable ContentRecordingSession session
+ ) {
+ }
}
/** @hide */
@@ -335,5 +351,13 @@
}
});
}
+
+ @Override
+ public void onRecordingSessionSet(
+ @NonNull final MediaProjectionInfo info,
+ @Nullable final ContentRecordingSession session
+ ) {
+ mHandler.post(() -> mCallback.onRecordingSessionSet(info, session));
+ }
}
}
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
index 917276b..ad18a9d 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.html
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
@@ -81,5 +81,7 @@
Dismiss flow
</button>
<p id="dismiss_flow"></p>
+
+ <h2>Test <a href="http://www.google.com">hyperlink</a></h2>
</body>
</html>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index 2530257d6..b100980 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -29,6 +29,7 @@
import android.view.KeyEvent;
import android.webkit.CookieManager;
import android.webkit.WebView;
+import android.webkit.WebViewClient;
import com.android.phone.slice.SlicePurchaseController;
@@ -113,8 +114,10 @@
return;
}
- // Create and configure WebView
- setupWebView();
+ // Clear any cookies that might be persisted from previous sessions before loading WebView
+ CookieManager.getInstance().removeAllCookies(value -> {
+ setupWebView();
+ });
}
protected void onPurchaseSuccessful() {
@@ -176,12 +179,7 @@
private void setupWebView() {
// Create WebView
mWebView = new WebView(this);
-
- // Clear any cookies and state that might be saved from previous sessions
- CookieManager.getInstance().removeAllCookies(null);
- CookieManager.getInstance().flush();
- mWebView.clearCache(true);
- mWebView.clearHistory();
+ mWebView.setWebViewClient(new WebViewClient());
// Enable JavaScript for the carrier purchase website to send results back to
// the slice purchase application.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 2e6bb53..f522fd1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -583,7 +583,7 @@
*/
public void setName(String name) {
// Prevent getName() to be set to null if setName(null) is called
- if (name == null || TextUtils.equals(name, getName())) {
+ if (TextUtils.isEmpty(name) || TextUtils.equals(name, getName())) {
return;
}
mDevice.setAlias(name);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index cd6609e..963bd9d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -15,6 +15,8 @@
*/
package com.android.settingslib.media;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
+
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
@@ -22,6 +24,7 @@
import android.media.AudioManager;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
+import android.media.RouteListingPreference;
import com.android.settingslib.R;
import com.android.settingslib.bluetooth.BluetoothUtils;
@@ -39,7 +42,13 @@
BluetoothMediaDevice(Context context, CachedBluetoothDevice device,
MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) {
- super(context, routerManager, info, packageName, null);
+ this(context, device, routerManager, info, packageName, null);
+ }
+
+ BluetoothMediaDevice(Context context, CachedBluetoothDevice device,
+ MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName,
+ RouteListingPreference.Item item) {
+ super(context, routerManager, info, packageName, item);
mCachedDevice = device;
mAudioManager = context.getSystemService(AudioManager.class);
initDeviceRecord();
@@ -58,6 +67,12 @@
}
@Override
+ public int getSelectionBehavior() {
+ // We don't allow apps to override the selection behavior of system routes.
+ return SELECTION_BEHAVIOR_TRANSFER;
+ }
+
+ @Override
public Drawable getIcon() {
return BluetoothUtils.isAdvancedUntetheredDevice(mCachedDevice.getDevice())
? mContext.getDrawable(R.drawable.ic_earbuds_advanced)
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 2a486a9..1728e40 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -568,8 +568,10 @@
case TYPE_HDMI:
case TYPE_WIRED_HEADSET:
case TYPE_WIRED_HEADPHONES:
- mediaDevice =
- new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName);
+ mediaDevice = mPreferenceItemMap.containsKey(route.getId()) ? new PhoneMediaDevice(
+ mContext, mRouterManager, route, mPackageName,
+ mPreferenceItemMap.get(route.getId())) : new PhoneMediaDevice(mContext,
+ mRouterManager, route, mPackageName);
break;
case TYPE_HEARING_AID:
case TYPE_BLUETOOTH_A2DP:
@@ -579,8 +581,11 @@
final CachedBluetoothDevice cachedDevice =
mBluetoothManager.getCachedDeviceManager().findDevice(device);
if (cachedDevice != null) {
- mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager,
- route, mPackageName);
+ mediaDevice = mPreferenceItemMap.containsKey(route.getId())
+ ? new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager,
+ route, mPackageName, mPreferenceItemMap.get(route.getId()))
+ : new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager,
+ route, mPackageName);
}
break;
case TYPE_REMOTE_AUDIO_VIDEO_RECEIVER:
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 34519c9..accd88c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -24,10 +24,13 @@
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
+
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
+import android.media.RouteListingPreference;
import androidx.annotation.VisibleForTesting;
@@ -51,7 +54,12 @@
PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
String packageName) {
- super(context, routerManager, info, packageName, null);
+ this(context, routerManager, info, packageName, null);
+ }
+
+ PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
+ String packageName, RouteListingPreference.Item item) {
+ super(context, routerManager, info, packageName, item);
mDeviceIconUtil = new DeviceIconUtil();
initDeviceRecord();
}
@@ -86,6 +94,12 @@
}
@Override
+ public int getSelectionBehavior() {
+ // We don't allow apps to override the selection behavior of system routes.
+ return SELECTION_BEHAVIOR_TRANSFER;
+ }
+
+ @Override
public String getSummary() {
return mSummary;
}
diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp
index 5c55a43..c037c40 100644
--- a/packages/SettingsLib/tests/robotests/Android.bp
+++ b/packages/SettingsLib/tests/robotests/Android.bp
@@ -42,7 +42,10 @@
name: "SettingsLibRoboTests",
srcs: ["src/**/*.java"],
static_libs: [
+ "Settings_robolectric_meta_service_file",
+ "Robolectric_shadows_androidx_fragment_upstream",
"SettingsLib-robo-testutils",
+ "androidx.fragment_fragment",
"androidx.test.core",
"androidx.core_core",
"testng", // TODO: remove once JUnit on Android provides assertThrows
@@ -53,6 +56,20 @@
test_options: {
timeout: 36000,
},
+ upstream: true,
+}
+
+java_genrule {
+ name: "Settings_robolectric_meta_service_file",
+ out: ["robolectric_meta_service_file.jar"],
+ tools: ["soong_zip"],
+ cmd: "mkdir -p $(genDir)/META-INF/services/ && touch $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider &&" +
+ "echo -e 'org.robolectric.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+ "echo -e 'org.robolectric.shadows.multidex.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+ "echo -e 'org.robolectric.shadows.httpclient.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+ //"echo -e 'com.android.settings.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+ "echo -e 'com.android.settingslib.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " +
+ "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)/META-INF/services/",
}
java_library {
@@ -60,9 +77,23 @@
srcs: [
"testutils/com/android/settingslib/testutils/**/*.java",
],
-
+ javacflags: [
+ "-Aorg.robolectric.annotation.processing.shadowPackage=com.android.settingslib.testutils.shadow",
+ "-Aorg.robolectric.annotation.processing.sdkCheckMode=ERROR",
+ // Uncomment the below to debug annotation processors not firing.
+ //"-verbose",
+ //"-XprintRounds",
+ //"-XprintProcessorInfo",
+ //"-Xlint",
+ //"-J-verbose",
+ ],
+ plugins: [
+ "auto_value_plugin_1.9",
+ "auto_value_builder_plugin_1.9",
+ "Robolectric_processor_upstream",
+ ],
libs: [
- "Robolectric_all-target",
+ "Robolectric_all-target_upstream",
"mockito-robolectric-prebuilt",
"truth-prebuilt",
],
diff --git a/packages/SettingsLib/tests/robotests/config/robolectric.properties b/packages/SettingsLib/tests/robotests/config/robolectric.properties
index fab7251..2a9e50d 100644
--- a/packages/SettingsLib/tests/robotests/config/robolectric.properties
+++ b/packages/SettingsLib/tests/robotests/config/robolectric.properties
@@ -1 +1,2 @@
sdk=NEWEST_SDK
+instrumentedPackages=androidx.preference
diff --git a/packages/SettingsLib/tests/robotests/fragment/Android.bp b/packages/SettingsLib/tests/robotests/fragment/Android.bp
new file mode 100644
index 0000000..3e67156
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/Android.bp
@@ -0,0 +1,40 @@
+//#############################################
+// Compile Robolectric shadows framework misapplied to androidx
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library {
+ name: "Robolectric_shadows_androidx_fragment_upstream",
+ srcs: [
+ "src/main/java/**/*.java",
+ "src/main/java/**/*.kt",
+ ],
+ javacflags: [
+ "-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.androidx.fragment",
+ "-Aorg.robolectric.annotation.processing.sdkCheckMode=ERROR",
+ // Uncomment the below to debug annotation processors not firing.
+ //"-verbose",
+ //"-XprintRounds",
+ //"-XprintProcessorInfo",
+ //"-Xlint",
+ //"-J-verbose",
+ ],
+ libs: [
+ "Robolectric_all-target_upstream",
+ "androidx.fragment_fragment",
+ ],
+ plugins: [
+ "auto_value_plugin_1.9",
+ "auto_value_builder_plugin_1.9",
+ "Robolectric_processor_upstream",
+ ],
+
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/BUILD b/packages/SettingsLib/tests/robotests/fragment/BUILD
new file mode 100644
index 0000000..393a02e
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/BUILD
@@ -0,0 +1,69 @@
+load("//third_party/java/android/android_sdk_linux/extras/android/compatibility/jetify:jetify.bzl", "jetify_android_library", "jetify_android_local_test")
+
+package(
+ default_applicable_licenses = ["//third_party/java_src/robolectric:license"],
+ default_visibility = ["//third_party/java_src/robolectric:__subpackages__"],
+)
+
+licenses(["notice"])
+
+#==============================================================================
+# Test resources library
+#==============================================================================
+jetify_android_library(
+ name = "test_resources",
+ custom_package = "org.robolectric.shadows.androidx.fragment",
+ manifest = "src/test/AndroidManifest.xml",
+ resource_files = glob(
+ ["src/test/resources/**/*"],
+ ),
+)
+
+#==============================================================================
+# AndroidX fragment module library
+#==============================================================================
+jetify_android_library(
+ name = "androidx_fragment",
+ testonly = 1,
+ srcs = glob(
+ ["src/main/java/**"],
+ ),
+ custom_package = "org.robolectric.shadows.androidx.fragment",
+ javacopts = [
+ "-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.androidx.fragment",
+ ],
+ jetify_sources = True,
+ plugins = [
+ "//java/com/google/thirdparty/robolectric/processor",
+ ],
+ deps = [
+ "//third_party/java/androidx/core",
+ "//third_party/java/androidx/fragment",
+ "//third_party/java/androidx/lifecycle",
+ "//third_party/java_src/robolectric/shadowapi",
+ "//third_party/java_src/robolectric/shadows/framework",
+ ],
+)
+
+[
+ jetify_android_local_test(
+ name = "test_" + src.rstrip(".java"),
+ size = "small",
+ srcs = glob(
+ ["src/test/java/**/*.java"],
+ ),
+ jetify_sources = True,
+ deps = [
+ ":androidx_fragment",
+ ":test_resources",
+ "//third_party/java/androidx/fragment",
+ "//third_party/java/androidx/loader",
+ "//third_party/java/mockito",
+ "//third_party/java/robolectric",
+ "//third_party/java/truth",
+ ],
+ )
+ for src in glob(
+ ["src/test/java/**/*Test.java"],
+ )
+]
diff --git a/packages/SettingsLib/tests/robotests/fragment/build.gradle b/packages/SettingsLib/tests/robotests/fragment/build.gradle
new file mode 100644
index 0000000..d9dcd84
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/build.gradle
@@ -0,0 +1,48 @@
+plugins {
+ id "net.ltgt.errorprone" version "0.0.13"
+}
+
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 28
+
+ android {
+ sourceSets {
+ main {
+ res.srcDirs = ['src/test/resources/res']
+ }
+ }
+ testOptions {
+ unitTests {
+ includeAndroidResources = true
+ }
+ }
+ }
+}
+
+dependencies {
+ // Project dependencies
+ compileOnly project(":robolectric")
+
+ // Compile dependencies
+ compileOnly AndroidSdk.MAX_SDK.coordinates
+ compileOnly "androidx.core:core:1.0.0-rc02"
+ compileOnly 'androidx.fragment:fragment:1.0.0-rc02'
+ compileOnly "androidx.lifecycle:lifecycle-viewmodel:2.0.0-rc01"
+ compileOnly "androidx.lifecycle:lifecycle-common:2.0.0-beta01"
+
+ // Testing dependencies
+ testImplementation "com.google.truth:truth:0.44"
+ testImplementation "org.mockito:mockito-core:2.5.4"
+ testImplementation "androidx.arch.core:core-common:2.0.0-beta01"
+ testImplementation "androidx.arch.core:core-runtime:2.0.0-rc01"
+ testImplementation "androidx.collection:collection:1.0.0-rc01"
+ testImplementation "androidx.core:core:1.0.0-rc02"
+ testImplementation 'androidx.fragment:fragment:1.0.0-rc02'
+ testImplementation "androidx.lifecycle:lifecycle-viewmodel:2.0.0-rc01"
+ testImplementation "androidx.lifecycle:lifecycle-common:2.0.0-beta01"
+ testImplementation "androidx.lifecycle:lifecycle-runtime:2.0.0-rc01"
+ testImplementation "androidx.lifecycle:lifecycle-livedata-core:2.0.0-rc01"
+ testImplementation "androidx.loader:loader:1.0.0-rc02"
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java
new file mode 100644
index 0000000..c688683
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2023 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 org.robolectric.shadows.androidx.fragment;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.android.controller.ComponentController;
+import org.robolectric.util.ReflectionHelpers;
+
+/** A Controller that can be used to drive the lifecycle of a {@link Fragment} */
+public class FragmentController<F extends Fragment>
+ extends ComponentController<FragmentController<F>, F> {
+
+ private final F mFragment;
+ private final ActivityController<? extends FragmentActivity> mActivityController;
+
+ private FragmentController(F fragment, Class<? extends FragmentActivity> activityClass) {
+ this(fragment, activityClass, null /*intent*/, null /*arguments*/);
+ }
+
+ private FragmentController(
+ F fragment, Class<? extends FragmentActivity> activityClass, Intent intent) {
+ this(fragment, activityClass, intent, null /*arguments*/);
+ }
+
+ private FragmentController(
+ F fragment, Class<? extends FragmentActivity> activityClass, Bundle arguments) {
+ this(fragment, activityClass, null /*intent*/, arguments);
+ }
+
+ private FragmentController(
+ F fragment,
+ Class<? extends FragmentActivity> activityClass,
+ Intent intent,
+ Bundle arguments) {
+ super(fragment, intent);
+ this.mFragment = fragment;
+ if (arguments != null) {
+ this.mFragment.setArguments(arguments);
+ }
+ this.mActivityController =
+ ActivityController.of(ReflectionHelpers.callConstructor(activityClass), intent);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(F fragment) {
+ return new FragmentController<>(fragment, FragmentControllerActivity.class);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment and intent.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @param intent the intent which will be retained by activity
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(F fragment, Intent intent) {
+ return new FragmentController<>(fragment, FragmentControllerActivity.class, intent);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment and arguments.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @param arguments the arguments which will be retained by fragment
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(F fragment, Bundle arguments) {
+ return new FragmentController<>(fragment, FragmentControllerActivity.class, arguments);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment and activity class.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @param activityClass the activity which will be attached by fragment
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(
+ F fragment, Class<? extends FragmentActivity> activityClass) {
+ return new FragmentController<>(fragment, activityClass);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment, intent and arguments.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @param intent the intent which will be retained by activity
+ * @param arguments the arguments which will be retained by fragment
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(
+ F fragment, Intent intent, Bundle arguments) {
+ return new FragmentController<>(fragment, FragmentControllerActivity.class, intent,
+ arguments);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment, activity class and intent.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @param activityClass the activity which will be attached by fragment
+ * @param intent the intent which will be retained by activity
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(
+ F fragment, Class<? extends FragmentActivity> activityClass, Intent intent) {
+ return new FragmentController<>(fragment, activityClass, intent);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment, activity class and arguments.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @param activityClass the activity which will be attached by fragment
+ * @param arguments the arguments which will be retained by fragment
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(
+ F fragment, Class<? extends FragmentActivity> activityClass, Bundle arguments) {
+ return new FragmentController<>(fragment, activityClass, arguments);
+ }
+
+ /**
+ * Generate the {@link FragmentController} for specific fragment, activity class, intent and
+ * arguments.
+ *
+ * @param fragment the fragment which you'd like to drive lifecycle
+ * @param activityClass the activity which will be attached by fragment
+ * @param intent the intent which will be retained by activity
+ * @param arguments the arguments which will be retained by fragment
+ * @return {@link FragmentController}
+ */
+ public static <F extends Fragment> FragmentController<F> of(
+ F fragment,
+ Class<? extends FragmentActivity> activityClass,
+ Intent intent,
+ Bundle arguments) {
+ return new FragmentController<>(fragment, activityClass, intent, arguments);
+ }
+
+ /**
+ * Sets up the given fragment by attaching it to an activity, calling its onCreate() through
+ * onResume() lifecycle methods, and then making it visible. Note that the fragment will be
+ * added
+ * to the view with ID 1.
+ */
+ public static <F extends Fragment> F setupFragment(F fragment) {
+ return FragmentController.of(fragment).create().start().resume().visible().get();
+ }
+
+ /**
+ * Sets up the given fragment by attaching it to an activity, calling its onCreate() through
+ * onResume() lifecycle methods, and then making it visible. Note that the fragment will be
+ * added
+ * to the view with ID 1.
+ */
+ public static <F extends Fragment> F setupFragment(
+ F fragment, Class<? extends FragmentActivity> fragmentActivityClass) {
+ return FragmentController.of(fragment, fragmentActivityClass)
+ .create()
+ .start()
+ .resume()
+ .visible()
+ .get();
+ }
+
+ /**
+ * Sets up the given fragment by attaching it to an activity created with the given bundle,
+ * calling its onCreate() through onResume() lifecycle methods, and then making it visible. Note
+ * that the fragment will be added to the view with ID 1.
+ */
+ public static <F extends Fragment> F setupFragment(
+ F fragment, Class<? extends FragmentActivity> fragmentActivityClass, Bundle bundle) {
+ return FragmentController.of(fragment, fragmentActivityClass)
+ .create(bundle)
+ .start()
+ .resume()
+ .visible()
+ .get();
+ }
+
+ /**
+ * Sets up the given fragment by attaching it to an activity created with the given bundle and
+ * container id, calling its onCreate() through onResume() lifecycle methods, and then making it
+ * visible.
+ */
+ public static <F extends Fragment> F setupFragment(
+ F fragment,
+ Class<? extends FragmentActivity> fragmentActivityClass,
+ int containerViewId,
+ Bundle bundle) {
+ return FragmentController.of(fragment, fragmentActivityClass)
+ .create(containerViewId, bundle)
+ .start()
+ .resume()
+ .visible()
+ .get();
+ }
+
+ /**
+ * Creates the activity with {@link Bundle} and adds the fragment to the view with ID {@code
+ * contentViewId}.
+ */
+ public FragmentController<F> create(final int contentViewId, final Bundle bundle) {
+ shadowMainLooper.runPaused(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivityController
+ .create(bundle)
+ .get()
+ .getSupportFragmentManager()
+ .beginTransaction()
+ .add(contentViewId, mFragment)
+ .commit();
+ }
+ });
+ return this;
+ }
+
+ /**
+ * Creates the activity with {@link Bundle} and adds the fragment to it. Note that the fragment
+ * will be added to the view with ID 1.
+ */
+ public FragmentController<F> create(final Bundle bundle) {
+ return create(1, bundle);
+ }
+
+ /**
+ * Creates the {@link Fragment} in a newly initialized state and hence will receive a null
+ * savedInstanceState {@link Bundle parameter}
+ */
+ @Override
+ public FragmentController<F> create() {
+ return create(null);
+ }
+
+ /** Drive lifecycle of activity to Start lifetime */
+ public FragmentController<F> start() {
+ shadowMainLooper.runPaused(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivityController.start();
+ }
+ });
+ return this;
+ }
+
+ /** Drive lifecycle of activity to Resume lifetime */
+ public FragmentController<F> resume() {
+ shadowMainLooper.runPaused(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivityController.resume();
+ }
+ });
+ return this;
+ }
+
+ /** Drive lifecycle of activity to Pause lifetime */
+ public FragmentController<F> pause() {
+ shadowMainLooper.runPaused(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivityController.pause();
+ }
+ });
+ return this;
+ }
+
+ /** Drive lifecycle of activity to Stop lifetime */
+ public FragmentController<F> stop() {
+ shadowMainLooper.runPaused(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivityController.stop();
+ }
+ });
+ return this;
+ }
+
+ /** Drive lifecycle of activity to Destroy lifetime */
+ @Override
+ public FragmentController<F> destroy() {
+ shadowMainLooper.runPaused(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivityController.destroy();
+ }
+ });
+ return this;
+ }
+
+ /** Let activity can be visible lifetime */
+ public FragmentController<F> visible() {
+ shadowMainLooper.runPaused(
+ new Runnable() {
+ @Override
+ public void run() {
+ mActivityController.visible();
+ }
+ });
+ return this;
+ }
+
+ private static class FragmentControllerActivity extends FragmentActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout view = new LinearLayout(this);
+ view.setId(1);
+
+ setContentView(view);
+ }
+ }
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java
similarity index 65%
copy from packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
copy to packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java
index 2a55a80..dd89441 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
+++ b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.testing.screenshot
-
-import androidx.activity.ComponentActivity
-
-/** The Activity that is launched and whose content is set for screenshot tests. */
-class ScreenshotActivity : ComponentActivity()
+/**
+ * Testing infrastructure for androidx.fragment library.
+ *
+ * <p>To use this in your project, add the artifact {@code
+ * org.robolectric:shadows-androidx-fragment} to your project.
+ */
+package org.robolectric.shadows.androidx.fragment;
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml
new file mode 100644
index 0000000..8493c02
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.robolectric.shadows.androidx.fragment">
+
+ <uses-sdk android:targetSdkVersion="28"/>
+</manifest>
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java b/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java
new file mode 100644
index 0000000..ef63058
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2023 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 org.robolectric.shadows.androidx.fragment;
+
+import static android.os.Looper.getMainLooper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests for {@link FragmentController} */
+@RunWith(RobolectricTestRunner.class)
+public class FragmentControllerTest {
+
+ @After
+ public void tearDown() {
+ TranscriptFragment.clearLifecycleEvents();
+ }
+
+ @Test
+ public void initialNotAttached() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment());
+
+ assertThat(controller.get().getView()).isNull();
+ assertThat(controller.get().getActivity()).isNull();
+ assertThat(controller.get().isAdded()).isFalse();
+ }
+
+ @Test
+ public void initialNotAttached_customActivity() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+ assertThat(controller.get().getView()).isNull();
+ assertThat(controller.get().getActivity()).isNull();
+ assertThat(controller.get().isAdded()).isFalse();
+ }
+
+ @Test
+ public void attachedAfterCreate() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment());
+
+ controller.create();
+ shadowOf(getMainLooper()).idle();
+
+ assertThat(controller.get().getActivity()).isNotNull();
+ assertThat(controller.get().isAdded()).isTrue();
+ assertThat(controller.get().isResumed()).isFalse();
+ }
+
+ @Test
+ public void attachedAfterCreate_customActivity() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+ controller.create();
+ shadowOf(getMainLooper()).idle();
+
+ assertThat(controller.get().getActivity()).isNotNull();
+ assertThat(controller.get().getActivity()).isInstanceOf(TestActivity.class);
+ assertThat(controller.get().isAdded()).isTrue();
+ assertThat(controller.get().isResumed()).isFalse();
+ }
+
+ @Test
+ public void attachedAfterCreate_customizedViewId() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), CustomizedViewIdTestActivity.class);
+
+ controller.create(R.id.custom_activity_view, null).start();
+
+ assertThat(controller.get().getView()).isNotNull();
+ assertThat(controller.get().getActivity()).isNotNull();
+ assertThat(controller.get().isAdded()).isTrue();
+ assertThat(controller.get().isResumed()).isFalse();
+ assertThat((TextView) controller.get().getView().findViewById(R.id.tacos)).isNotNull();
+ }
+
+ @Test
+ public void hasViewAfterStart() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment());
+
+ controller.create().start();
+
+ assertThat(controller.get().getView()).isNotNull();
+ }
+
+ @Test
+ public void isResumed() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+ controller.create().start().resume();
+
+ assertThat(controller.get().getView()).isNotNull();
+ assertThat(controller.get().getActivity()).isNotNull();
+ assertThat(controller.get().isAdded()).isTrue();
+ assertThat(controller.get().isResumed()).isTrue();
+ assertThat((TextView) controller.get().getView().findViewById(R.id.tacos)).isNotNull();
+ }
+
+ @Test
+ public void isPaused() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+ controller.create().start().resume().pause();
+
+ assertThat(controller.get().getView()).isNotNull();
+ assertThat(controller.get().getActivity()).isNotNull();
+ assertThat(controller.get().isAdded()).isTrue();
+ assertThat(controller.get().isResumed()).isFalse();
+ assertThat(controller.get().getLifecycleEvents())
+ .containsExactly("onCreate", "onStart", "onResume", "onPause")
+ .inOrder();
+ }
+
+ @Test
+ public void isStopped() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+ controller.create().start().resume().pause().stop();
+
+ assertThat(controller.get().getView()).isNotNull();
+ assertThat(controller.get().getActivity()).isNotNull();
+ assertThat(controller.get().isAdded()).isTrue();
+ assertThat(controller.get().isResumed()).isFalse();
+ assertThat(controller.get().getLifecycleEvents())
+ .containsExactly("onCreate", "onStart", "onResume", "onPause", "onStop")
+ .inOrder();
+ }
+
+ @Test
+ public void withIntent() {
+ final Intent intent = generateTestIntent();
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class, intent);
+
+ controller.create();
+ shadowOf(getMainLooper()).idle();
+ final Intent intentInFragment = controller.get().getActivity().getIntent();
+
+ assertThat(intentInFragment.getAction()).isEqualTo("test_action");
+ assertThat(intentInFragment.getExtras().getString("test_key")).isEqualTo("test_value");
+ }
+
+ @Test
+ public void withArguments() {
+ final Bundle bundle = generateTestBundle();
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class, bundle);
+
+ controller.create();
+ final Bundle args = controller.get().getArguments();
+
+ assertThat(args.getString("test_key")).isEqualTo("test_value");
+ }
+
+ @Test
+ public void withIntentAndArguments() {
+ final Bundle bundle = generateTestBundle();
+ final Intent intent = generateTestIntent();
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class, intent, bundle);
+
+ controller.create();
+ shadowOf(getMainLooper()).idle();
+ final Intent intentInFragment = controller.get().getActivity().getIntent();
+ final Bundle args = controller.get().getArguments();
+
+ assertThat(intentInFragment.getAction()).isEqualTo("test_action");
+ assertThat(intentInFragment.getExtras().getString("test_key")).isEqualTo("test_value");
+ assertThat(args.getString("test_key")).isEqualTo("test_value");
+ }
+
+ @Test
+ public void visible() {
+ final FragmentController<TranscriptFragment> controller =
+ FragmentController.of(new TranscriptFragment(), TestActivity.class);
+
+ controller.create().start().resume();
+
+ assertThat(controller.get().isVisible()).isFalse();
+
+ controller.visible();
+
+ assertThat(controller.get().isVisible()).isTrue();
+ }
+
+ @Test
+ public void setupFragmentWithFragment_fragmentHasCorrectLifecycle() {
+ TranscriptFragment fragment = FragmentController.setupFragment(new TranscriptFragment());
+
+ assertThat(fragment.getLifecycleEvents())
+ .containsExactly("onCreate", "onStart", "onResume")
+ .inOrder();
+ assertThat(fragment.isVisible()).isTrue();
+ }
+
+ @Test
+ public void setupFragmentWithFragmentAndActivity_fragmentHasCorrectLifecycle() {
+ TranscriptFragment fragment =
+ FragmentController.setupFragment(new TranscriptFragment(), TestActivity.class);
+
+ assertThat(fragment.getLifecycleEvents())
+ .containsExactly("onCreate", "onStart", "onResume")
+ .inOrder();
+ assertThat(fragment.isVisible()).isTrue();
+ }
+
+ @Test
+ public void setupFragmentWithFragmentAndActivityAndBundle_HasCorrectLifecycle() {
+ Bundle testBundle = generateTestBundle();
+ TranscriptFragment fragment =
+ FragmentController.setupFragment(new TranscriptFragment(), TestActivity.class,
+ testBundle);
+
+ assertThat(fragment.getLifecycleEvents())
+ .containsExactly("onCreate", "onStart", "onResume")
+ .inOrder();
+ assertThat(fragment.isVisible()).isTrue();
+ }
+
+ @Test
+ public void
+ setupFragmentWithFragment_Activity_ContainViewIdAndBundle_HasCorrectLifecycle() {
+ Bundle testBundle = generateTestBundle();
+ TranscriptFragment fragment =
+ FragmentController.setupFragment(
+ new TranscriptFragment(),
+ CustomizedViewIdTestActivity.class,
+ R.id.custom_activity_view,
+ testBundle);
+
+ assertThat(fragment.getLifecycleEvents())
+ .containsExactly("onCreate", "onStart", "onResume")
+ .inOrder();
+ assertThat(fragment.isVisible()).isTrue();
+ }
+
+ private Intent generateTestIntent() {
+ final Intent testIntent = new Intent("test_action").putExtra("test_key", "test_value");
+ return testIntent;
+ }
+
+ private Bundle generateTestBundle() {
+ final Bundle testBundle = new Bundle();
+ testBundle.putString("test_key", "test_value");
+
+ return testBundle;
+ }
+
+ /** A Fragment which can record lifecycle status for test. */
+ public static class TranscriptFragment extends Fragment {
+
+ public static final List<String> sLifecycleEvents = new ArrayList<>();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ sLifecycleEvents.add("onCreate");
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ sLifecycleEvents.add("onStart");
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ sLifecycleEvents.add("onResume");
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ sLifecycleEvents.add("onPause");
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ sLifecycleEvents.add("onStop");
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_contents, container, false);
+ }
+
+ public List<String> getLifecycleEvents() {
+ return sLifecycleEvents;
+ }
+
+ public static void clearLifecycleEvents() {
+ sLifecycleEvents.clear();
+ }
+ }
+
+ /** A Activity which set a default view for test. */
+ public static class TestActivity extends FragmentActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout view = new LinearLayout(this);
+ view.setId(1);
+
+ setContentView(view);
+ }
+ }
+
+ /** A Activity which has a custom view for test. */
+ public static class CustomizedViewIdTestActivity extends FragmentActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.custom_activity_view);
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml
new file mode 100644
index 0000000..c074f30
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/custom_activity_view"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+</LinearLayout>
diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml
new file mode 100644
index 0000000..425b2bb
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/tacos"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="TACOS"/>
+
+ <TextView
+ android:id="@+id/burritos"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="BURRITOS"/>
+
+</LinearLayout>
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 4a913c8..bb72375 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -25,7 +25,6 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -58,12 +57,10 @@
import org.robolectric.shadows.ShadowSettings;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {UtilsTest.ShadowSecure.class, UtilsTest.ShadowLocationManager.class})
+@Config(shadows = {UtilsTest.ShadowLocationManager.class})
public class UtilsTest {
private static final double[] TEST_PERCENTAGES = {0, 0.4, 0.5, 0.6, 49, 49.3, 49.8, 50, 100};
private static final String TAG = "UtilsTest";
@@ -94,7 +91,7 @@
mContext = spy(RuntimeEnvironment.application);
when(mContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager);
when(mContext.getSystemService(UsbManager.class)).thenReturn(mUsbManager);
- ShadowSecure.reset();
+ ShadowSettings.ShadowSecure.reset();
mAudioManager = mContext.getSystemService(AudioManager.class);
}
@@ -111,15 +108,16 @@
Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.LOCATION_CHANGER, Settings.Secure.LOCATION_CHANGER_UNKNOWN))
- .isEqualTo(Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
+ Settings.Secure.LOCATION_CHANGER,
+ Settings.Secure.LOCATION_CHANGER_UNKNOWN)).isEqualTo(
+ Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
}
@Test
public void testFormatPercentage_RoundTrue_RoundUpIfPossible() {
- final String[] expectedPercentages = {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1,
- PERCENTAGE_1, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50,
- PERCENTAGE_100};
+ final String[] expectedPercentages =
+ {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1, PERCENTAGE_1, PERCENTAGE_49,
+ PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50, PERCENTAGE_100};
for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], true);
@@ -129,9 +127,9 @@
@Test
public void testFormatPercentage_RoundFalse_NoRound() {
- final String[] expectedPercentages = {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0,
- PERCENTAGE_0, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50,
- PERCENTAGE_100};
+ final String[] expectedPercentages =
+ {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_49,
+ PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_100};
for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], false);
@@ -143,12 +141,7 @@
public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() {
Resources resources = mock(Resources.class);
when(resources.getInteger(
- eq(
- com.android
- .internal
- .R
- .integer
- .config_storageManagerDaystoRetainDefault)))
+ eq(com.android.internal.R.integer.config_storageManagerDaystoRetainDefault)))
.thenReturn(60);
assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60);
}
@@ -163,31 +156,6 @@
return intent -> TextUtils.equals(expected, intent.getAction());
}
- @Implements(value = Settings.Secure.class)
- public static class ShadowSecure extends ShadowSettings.ShadowSecure {
- private static Map<String, Integer> map = new HashMap<>();
-
- @Implementation
- public static boolean putIntForUser(ContentResolver cr, String name, int value,
- int userHandle) {
- map.put(name, value);
- return true;
- }
-
- @Implementation
- public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
- if (map.containsKey(name)) {
- return map.get(name);
- } else {
- return def;
- }
- }
-
- public static void reset() {
- map.clear();
- }
- }
-
@Implements(value = LocationManager.class)
public static class ShadowLocationManager {
@@ -337,9 +305,8 @@
@Test
public void getBatteryStatus_statusIsFull_returnFullString() {
- final Intent intent = new Intent()
- .putExtra(BatteryManager.EXTRA_LEVEL, 100)
- .putExtra(BatteryManager.EXTRA_SCALE, 100);
+ final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
+ BatteryManager.EXTRA_SCALE, 100);
final Resources resources = mContext.getResources();
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
@@ -348,9 +315,8 @@
@Test
public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() {
- final Intent intent = new Intent()
- .putExtra(BatteryManager.EXTRA_LEVEL, 100)
- .putExtra(BatteryManager.EXTRA_SCALE, 100);
+ final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
+ BatteryManager.EXTRA_SCALE, 100);
final Resources resources = mContext.getResources();
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
@@ -516,7 +482,6 @@
when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
when(mUsbPortStatus.isConnected()).thenReturn(true);
- when(mUsbPortStatus.getComplianceWarnings())
- .thenReturn(new int[]{complianceWarningType});
+ when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{complianceWarningType});
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
index 44fdaec..3de8446 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
@@ -23,13 +23,17 @@
import android.os.UserHandle;
import android.provider.Settings;
+import com.android.settingslib.testutils.shadow.ShadowSecure;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowSecure.class})
public class AccessibilityUtilsTest {
private Context mContext;
@@ -46,7 +50,7 @@
@Test
public void getEnabledServicesFromSettings_badFormat_emptyResult() {
- Settings.Secure.putStringForUser(
+ ShadowSecure.putStringForUser(
mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
":",
UserHandle.myUserId());
@@ -57,7 +61,7 @@
@Test
public void getEnabledServicesFromSettings_1Service_1result() {
final ComponentName cn = new ComponentName("pkg", "serv");
- Settings.Secure.putStringForUser(
+ ShadowSecure.putStringForUser(
mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
cn.flattenToString() + ":",
UserHandle.myUserId());
@@ -70,7 +74,7 @@
public void getEnabledServicesFromSettings_2Services_2results() {
final ComponentName cn1 = new ComponentName("pkg", "serv");
final ComponentName cn2 = new ComponentName("pkg", "serv2");
- Settings.Secure.putStringForUser(
+ ShadowSecure.putStringForUser(
mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
cn1.flattenToString() + ":" + cn2.flattenToString(),
UserHandle.myUserId());
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java
index cb62a73..f9505dd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java
@@ -37,6 +37,8 @@
import android.os.UserManager;
import android.util.LongSparseArray;
+import com.android.settingslib.testutils.shadow.ShadowPermissionChecker;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,7 +47,6 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowPermissionChecker;
import java.time.Clock;
import java.util.ArrayList;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 6444f3b..4b61ff1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1015,6 +1015,13 @@
}
@Test
+ public void setName_setDeviceNameIsEmpty() {
+ mCachedDevice.setName("");
+
+ verify(mDevice, never()).setAlias(any());
+ }
+
+ @Test
public void getProfileConnectionState_nullProfile_returnDisconnected() {
assertThat(mCachedDevice.getProfileConnectionState(null)).isEqualTo(
BluetoothProfile.STATE_DISCONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
index dd8d54a..a2e8c59 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -38,6 +38,7 @@
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
@@ -167,6 +168,7 @@
}
@Test
+ @LooperMode(LooperMode.Mode.PAUSED)
public void getAttribution_notSet_shouldReturnUnknown() {
final Activity activity = Robolectric.setupActivity(Activity.class);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
index d67d44b..25833b3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
@@ -36,6 +36,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.CujType;
+import com.android.settingslib.testutils.OverpoweredReflectionHelper;
import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor;
import org.junit.Before;
@@ -51,7 +52,6 @@
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
-import org.robolectric.util.ReflectionHelpers;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -83,8 +83,10 @@
public void setUp() {
ShadowInteractionJankMonitor.reset();
when(ShadowInteractionJankMonitor.MOCK_INSTANCE.begin(any())).thenReturn(true);
- ReflectionHelpers.setStaticField(SettingsJankMonitor.class, "scheduledExecutorService",
- mScheduledExecutorService);
+ OverpoweredReflectionHelper
+ .setStaticField(SettingsJankMonitor.class,
+ "scheduledExecutorService",
+ mScheduledExecutorService);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
index cf702b53..471dac0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
@@ -37,8 +37,10 @@
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.LooperMode;
@RunWith(RobolectricTestRunner.class)
+@LooperMode(LooperMode.Mode.PAUSED)
public class HideNonSystemOverlayMixinTest {
private ActivityController<TestActivity> mActivityController;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
index 3475ff7..b009abd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
@@ -22,13 +22,14 @@
import android.os.UserManager;
import android.provider.Settings;
+import com.android.settingslib.testutils.shadow.ShadowUserManager;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowUserManager;
@RunWith(RobolectricTestRunner.class)
public class DevelopmentSettingsEnablerTest {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
index 8e33ca3..0cabab2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
@@ -21,6 +21,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.LooperMode;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
@@ -269,6 +270,7 @@
}
@Test
+ @LooperMode(LooperMode.Mode.PAUSED)
public void testGenerateHtmlWithCustomHeading() throws Exception {
List<File> xmlFiles = new ArrayList<>();
Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
@@ -292,6 +294,7 @@
}
@Test
+ @LooperMode(LooperMode.Mode.PAUSED)
public void testGenerateNewHtmlWithCustomHeading() throws Exception {
List<File> xmlFiles = new ArrayList<>();
Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index c058a61..f22e090 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -19,6 +19,9 @@
import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP;
+
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
import static com.google.common.truth.Truth.assertThat;
@@ -32,6 +35,7 @@
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.media.NearbyDevice;
+import android.media.RouteListingPreference;
import android.os.Parcel;
import com.android.settingslib.bluetooth.A2dpProfile;
@@ -110,6 +114,8 @@
@Mock
private MediaRouter2Manager mMediaRouter2Manager;
+ private RouteListingPreference.Item mItem;
+
private BluetoothMediaDevice mBluetoothMediaDevice1;
private BluetoothMediaDevice mBluetoothMediaDevice2;
private BluetoothMediaDevice mBluetoothMediaDevice3;
@@ -497,4 +503,21 @@
assertThat(mBluetoothMediaDevice1.getFeatures().size()).isEqualTo(0);
}
+
+ @Test
+ public void getSelectionBehavior_setItemWithSelectionBehaviorOnSystemRoute_returnTransfer() {
+ mItem = new RouteListingPreference.Item.Builder(DEVICE_ADDRESS_1)
+ .setSelectionBehavior(SELECTION_BEHAVIOR_GO_TO_APP)
+ .build();
+ mBluetoothMediaDevice1 = new BluetoothMediaDevice(mContext, mCachedDevice1,
+ mMediaRouter2Manager, null /* MediaRoute2Info */, TEST_PACKAGE_NAME, mItem);
+ mPhoneMediaDevice =
+ new PhoneMediaDevice(mContext, mMediaRouter2Manager, mPhoneRouteInfo,
+ TEST_PACKAGE_NAME, mItem);
+
+ assertThat(mBluetoothMediaDevice1.getSelectionBehavior()).isEqualTo(
+ SELECTION_BEHAVIOR_TRANSFER);
+ assertThat(mPhoneMediaDevice.getSelectionBehavior()).isEqualTo(
+ SELECTION_BEHAVIOR_TRANSFER);
+ }
}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java
similarity index 66%
rename from packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
rename to packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java
index 2a55a80..9e9725f 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.testing.screenshot
+@LooperMode(LooperMode.Mode.LEGACY)
+package com.android.settingslib;
-import androidx.activity.ComponentActivity
-
-/** The Activity that is launched and whose content is set for screenshot tests. */
-class ScreenshotActivity : ComponentActivity()
+import org.robolectric.annotation.LooperMode;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
index d41d511..faec02f7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
@@ -27,6 +27,7 @@
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.LooperMode;
@RunWith(RobolectricTestRunner.class)
public class AnimatedImageViewTest {
@@ -40,6 +41,7 @@
}
@Test
+ @LooperMode(LooperMode.Mode.PAUSED)
public void testAnimation_ViewVisible_AnimationRunning() {
mAnimatedImageView.setVisibility(View.VISIBLE);
mAnimatedImageView.setAnimating(true);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
index 0a48f19..0d88913 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
@@ -41,6 +41,8 @@
import androidx.preference.PreferenceViewHolder;
import androidx.preference.R;
+import com.android.settingslib.testutils.OverpoweredReflectionHelper;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -502,14 +504,18 @@
private void assumeAndroidR() {
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 30);
ReflectionHelpers.setStaticField(Build.VERSION.class, "CODENAME", "R");
- ReflectionHelpers.setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", false);
+ OverpoweredReflectionHelper
+ .setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", false);
// Reset view holder to use correct layout.
}
+
+
private void assumeAndroidS() {
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 31);
ReflectionHelpers.setStaticField(Build.VERSION.class, "CODENAME", "S");
- ReflectionHelpers.setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", true);
+ OverpoweredReflectionHelper
+ .setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", true);
// Re-inflate view to update layout.
setUpViewHolder();
}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java
new file mode 100644
index 0000000..4fcc5a1
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 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.settingslib.testutils;
+
+import org.robolectric.util.ReflectionHelpers;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+public class OverpoweredReflectionHelper extends ReflectionHelpers {
+
+ /**
+ * Robolectric upstream does not rely on or encourage this behaviour.
+ *
+ * @param field
+ */
+ private static void makeFieldVeryAccessible(Field field) {
+ field.setAccessible(true);
+ // remove 'final' modifier if present
+ if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
+ Field modifiersField = getModifiersField();
+ modifiersField.setAccessible(true);
+ try {
+ modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+ } catch (IllegalAccessException e) {
+
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ private static Field getModifiersField() {
+ try {
+ return Field.class.getDeclaredField("modifiers");
+ } catch (NoSuchFieldException e) {
+ try {
+ Method getFieldsMethod =
+ Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
+ getFieldsMethod.setAccessible(true);
+ Field[] fields = (Field[]) getFieldsMethod.invoke(Field.class, false);
+ for (Field modifiersField : fields) {
+ if ("modifiers".equals(modifiersField.getName())) {
+ return modifiersField;
+ }
+ }
+ } catch (ReflectiveOperationException innerE) {
+ throw new AssertionError(innerE);
+ }
+ }
+ throw new AssertionError();
+ }
+
+ /**
+ * Reflectively set the value of a static field.
+ *
+ * @param field Field object.
+ * @param fieldNewValue The new value.
+ */
+ public static void setStaticField(Field field, Object fieldNewValue) {
+ try {
+ makeFieldVeryAccessible(field);
+ field.setAccessible(true);
+ field.set(null, fieldNewValue);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Reflectively set the value of a static field.
+ *
+ * @param clazz Target class.
+ * @param fieldName The field name.
+ * @param fieldNewValue The new value.
+ */
+ public static void setStaticField(Class<?> clazz, String fieldName, Object fieldNewValue) {
+ try {
+ setStaticField(clazz.getDeclaredField(fieldName), fieldNewValue);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
index 924eb04..0b9ba8d 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
@@ -16,23 +16,27 @@
package com.android.settingslib.testutils.shadow;
+import static android.os.Build.VERSION_CODES.O;
+
import android.app.ActivityManager;
+import android.app.IActivityManager;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
@Implements(ActivityManager.class)
public class ShadowActivityManager {
private static int sCurrentUserId = 0;
- private int mUserSwitchedTo = -1;
+ private static int sUserSwitchedTo = -1;
@Resetter
- public void reset() {
+ public static void reset() {
sCurrentUserId = 0;
- mUserSwitchedTo = 0;
+ sUserSwitchedTo = 0;
}
@Implementation
@@ -42,16 +46,21 @@
@Implementation
protected boolean switchUser(int userId) {
- mUserSwitchedTo = userId;
+ sUserSwitchedTo = userId;
return true;
}
+ @Implementation(minSdk = O)
+ protected static IActivityManager getService() {
+ return ReflectionHelpers.createNullProxy(IActivityManager.class);
+ }
+
public boolean getSwitchUserCalled() {
- return mUserSwitchedTo != -1;
+ return sUserSwitchedTo != -1;
}
public int getUserSwitchedTo() {
- return mUserSwitchedTo;
+ return sUserSwitchedTo;
}
public static void setCurrentUser(int userId) {
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
index 2c0792f..bbfdb7f 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
@@ -29,7 +29,7 @@
private static String sDefaultDialer;
@Resetter
- public void reset() {
+ public static void reset() {
sDefaultDialer = null;
}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
new file mode 100644
index 0000000..fae3aea
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 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.settingslib.testutils.shadow;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.content.PermissionChecker;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.HashMap;
+import java.util.Map;
+/** Shadow class of {@link PermissionChecker}. */
+@Implements(PermissionChecker.class)
+public class ShadowPermissionChecker {
+ private static final Map<String, Map<String, Integer>> RESULTS = new HashMap<>();
+ /** Set the result of permission check for a specific permission. */
+ public static void setResult(String packageName, String permission, int result) {
+ if (!RESULTS.containsKey(packageName)) {
+ RESULTS.put(packageName, new HashMap<>());
+ }
+ RESULTS.get(packageName).put(permission, result);
+ }
+ /** Check the permission of calling package. */
+ @Implementation
+ public static int checkCallingPermissionForDataDelivery(
+ Context context,
+ String permission,
+ String packageName,
+ String attributionTag,
+ String message) {
+ return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission)
+ ? RESULTS.get(packageName).get(permission)
+ : PermissionChecker.checkCallingPermissionForDataDelivery(
+ context, permission, packageName, attributionTag, message);
+ }
+ /** Check general permission. */
+ @Implementation
+ public static int checkPermissionForDataDelivery(
+ Context context,
+ String permission,
+ int pid,
+ int uid,
+ String packageName,
+ String attributionTag,
+ String message) {
+ return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission)
+ ? RESULTS.get(packageName).get(permission)
+ : PermissionChecker.checkPermissionForDataDelivery(
+ context, permission, pid, uid, packageName, attributionTag, message);
+ }
+ /** Check general permission. */
+ @Implementation
+ public static int checkPermissionForPreflight(@NonNull Context context,
+ @NonNull String permission, int pid, int uid, @Nullable String packageName) {
+ return checkPermissionForPreflight(context, permission, new AttributionSource(
+ uid, packageName, null /*attributionTag*/));
+ }
+ /** Check general permission. */
+ @Implementation
+ public static int checkPermissionForPreflight(@NonNull Context context,
+ @NonNull String permission, @NonNull AttributionSource attributionSource) {
+ final String packageName = attributionSource.getPackageName();
+ return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission)
+ ? RESULTS.get(packageName).get(permission)
+ : PermissionChecker.checkPermissionForPreflight(
+ context, permission, attributionSource);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java
new file mode 100644
index 0000000..70ebc67
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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.settingslib.testutils.shadow;
+
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowSettings;
+
+@Implements(value = Settings.Secure.class)
+public class ShadowSecure extends ShadowSettings.ShadowSecure {
+ @Implementation(minSdk = JELLY_BEAN_MR1)
+ public static boolean putStringForUser(ContentResolver cr, String name, String value,
+ int userHandle) {
+ return putString(cr, name, value);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
index 381d072..5ac0a87 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java
@@ -31,7 +31,7 @@
private static ComponentName sDefaultSmsApplication;
@Resetter
- public void reset() {
+ public static void reset() {
sDefaultSmsApplication = null;
}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java
index ca1eefc..60d7721 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java
@@ -16,20 +16,28 @@
package com.android.settingslib.testutils.shadow;
+import static android.os.Build.VERSION_CODES.N_MR1;
+
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
+import android.os.UserHandle;
import android.os.UserManager;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowBuild;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@Implements(value = UserManager.class)
public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager {
private List<UserInfo> mUserInfos = addProfile(0, "Owner");
+ private final Map<Integer, UserProperties> mUserPropertiesMap = new HashMap<>();
@Implementation
protected static UserManager get(Context context) {
@@ -62,4 +70,37 @@
protected List<UserInfo> getProfiles(@UserIdInt int userHandle) {
return getProfiles();
}
+
+ /**
+ * @return {@code false} by default, or the value specified via {@link #setIsAdminUser(boolean)}
+ */
+ @Implementation(minSdk = N_MR1)
+ public boolean isAdminUser() {
+ return getUserInfo(UserHandle.myUserId()).isAdmin();
+ }
+
+ /**
+ * Sets that the current user is an admin user; controls the return value of
+ * {@link UserManager#isAdminUser}.
+ */
+ public void setIsAdminUser(boolean isAdminUser) {
+ UserInfo userInfo = getUserInfo(UserHandle.myUserId());
+ if (isAdminUser) {
+ userInfo.flags |= UserInfo.FLAG_ADMIN;
+ } else {
+ userInfo.flags &= ~UserInfo.FLAG_ADMIN;
+ }
+ }
+
+ public void setupUserProperty(int userId, int showInSettings) {
+ UserProperties userProperties = new UserProperties(new UserProperties.Builder()
+ .setShowInSettings(showInSettings).build());
+ mUserPropertiesMap.putIfAbsent(userId, userProperties);
+ }
+
+ @Implementation(minSdk = ShadowBuild.UPSIDE_DOWN_CAKE)
+ protected UserProperties getUserProperties(UserHandle user) {
+ return mUserPropertiesMap.getOrDefault(user.getIdentifier(),
+ new UserProperties(new UserProperties.Builder().build()));
+ }
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index a443b5c..73fb0f0 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -65,6 +65,7 @@
"androidx.compose.runtime_runtime",
"androidx.compose.material3_material3",
"androidx.activity_activity-compose",
+ "androidx.compose.animation_animation-graphics",
],
// By default, Compose is disabled and we compile the ComposeFacade
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2913c16..4fd4723 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -865,7 +865,7 @@
<activity
android:name=".settings.brightness.BrightnessDialog"
android:label="@string/quick_settings_brightness_dialog_title"
- android:theme="@style/Theme.SystemUI.QuickSettings.BrightnessDialog"
+ android:theme="@style/BrightnessDialog"
android:finishOnCloseSystemDialogs="true"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
index fbd7f83..1674591 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
@@ -17,9 +17,9 @@
package com.android.systemui.compose
import android.view.View
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.ViewTreeLifecycleOwner
-import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
import com.android.compose.animation.ViewTreeSavedStateRegistryOwner
@@ -27,7 +27,7 @@
internal object ComposeInitializerImpl : ComposeInitializer {
override fun onAttachedToWindow(root: View) {
- if (ViewTreeLifecycleOwner.get(root) != null) {
+ if (root.findViewTreeLifecycleOwner() != null) {
error("root $root already has a LifecycleOwner")
}
@@ -54,7 +54,8 @@
override val savedStateRegistry = savedStateRegistryController.savedStateRegistry
- override fun getLifecycle(): Lifecycle = lifecycleOwner.lifecycle
+ override val lifecycle: Lifecycle
+ get() = lifecycleOwner.lifecycle
}
// We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner]
@@ -64,13 +65,13 @@
// Set the owners on the root. They will be reused by any ComposeView inside the root
// hierarchy.
- ViewTreeLifecycleOwner.set(root, lifecycleOwner)
+ root.setViewTreeLifecycleOwner(lifecycleOwner)
ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner)
}
override fun onDetachedFromWindow(root: View) {
- (ViewTreeLifecycleOwner.get(root) as ViewLifecycleOwner).onDestroy()
- ViewTreeLifecycleOwner.set(root, null)
+ (root.findViewTreeLifecycleOwner() as ViewLifecycleOwner).onDestroy()
+ root.setViewTreeLifecycleOwner(null)
ViewTreeSavedStateRegistryOwner.set(root, null)
}
}
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 8f8bb1b..c6438c9 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -34,6 +34,7 @@
"PlatformComposeCore",
"androidx.compose.runtime_runtime",
+ "androidx.compose.animation_animation-graphics",
"androidx.compose.material3_material3",
"androidx.activity_activity-compose",
],
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 240bace..d83596e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -99,7 +99,10 @@
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(60.dp),
modifier =
- modifier.background(MaterialTheme.colorScheme.surface).fillMaxSize().padding(32.dp)
+ modifier
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.surface)
+ .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
) {
Crossfade(
targetState = message,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 323fed0..85178bc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalAnimationApi::class)
+@file:OptIn(ExperimentalAnimationApi::class, ExperimentalAnimationGraphicsApi::class)
package com.android.systemui.bouncer.ui.composable
@@ -29,11 +29,14 @@
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
-import androidx.compose.foundation.Canvas
+import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -61,8 +64,10 @@
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.Constraints
@@ -70,6 +75,7 @@
import androidx.compose.ui.unit.dp
import com.android.compose.animation.Easings
import com.android.compose.grid.VerticalGrid
+import com.android.internal.R.id.image
import com.android.systemui.R
import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance
import com.android.systemui.bouncer.ui.viewmodel.EnteredKey
@@ -139,7 +145,8 @@
else -> EntryVisibility.Hidden
}
- ObscuredInputEntry(updateTransition(visibility, label = "Pin Entry $entry"))
+ val shape = viewModel.pinShapes.getShape(entry.sequenceNumber)
+ PinInputEntry(shape, updateTransition(visibility, label = "Pin Entry $entry"))
LaunchedEffect(entry) {
// Remove entry from visiblePinEntries once the hide transition completed.
@@ -171,15 +178,11 @@
}
@Composable
-private fun ObscuredInputEntry(transition: Transition<EntryVisibility>) {
+private fun PinInputEntry(shapeResourceId: Int, transition: Transition<EntryVisibility>) {
// spec: http://shortn/_DEhE3Xl2bi
- val shapePadding = 6.dp
- val shapeOvershootSize = 22.dp
val dismissStaggerDelayMs = 33
val dismissDurationMs = 450
val expansionDurationMs = 250
- val shapeExpandDurationMs = 83
- val shapeRetractDurationMs = 167
val shapeCollapseDurationMs = 200
val animatedEntryWidth by
@@ -194,19 +197,17 @@
},
label = "entry space"
) { state ->
- if (state == EntryVisibility.Shown) entryShapeSize + (shapePadding * 2) else 0.dp
+ if (state == EntryVisibility.Shown) entryShapeSize else 0.dp
}
val animatedShapeSize by
transition.animateDp(
transitionSpec = {
when {
- EntryVisibility.Hidden isTransitioningTo EntryVisibility.Shown ->
- keyframes {
- durationMillis = shapeExpandDurationMs + shapeRetractDurationMs
- 0.dp at 0 with Easings.Linear
- shapeOvershootSize at shapeExpandDurationMs with Easings.Legacy
- }
+ EntryVisibility.Hidden isTransitioningTo EntryVisibility.Shown -> {
+ // The AVD contains the entry transition.
+ snap()
+ }
targetState is EntryVisibility.BulkHidden -> {
val target = targetState as EntryVisibility.BulkHidden
tween(
@@ -220,17 +221,21 @@
},
label = "shape size"
) { state ->
- when (state) {
- EntryVisibility.Shown -> entryShapeSize
- else -> 0.dp
- }
+ if (state == EntryVisibility.Shown) entryShapeSize else 0.dp
}
val dotColor = MaterialTheme.colorScheme.onSurfaceVariant
Layout(
content = {
- // TODO(b/282730134): add support for dot shapes.
- Canvas(Modifier) { drawCircle(dotColor) }
+ val image = AnimatedImageVector.animatedVectorResource(shapeResourceId)
+ var atEnd by remember { mutableStateOf(false) }
+ Image(
+ painter = rememberAnimatedVectorPainter(image, atEnd),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ colorFilter = ColorFilter.tint(dotColor),
+ )
+ LaunchedEffect(Unit) { atEnd = true }
}
) { measurables, _ ->
val shapeSizePx = animatedShapeSize.roundToPx()
@@ -507,7 +512,7 @@
}
}
-private val entryShapeSize = 16.dp
+private val entryShapeSize = 30.dp
private val pinButtonSize = 84.dp
private val pinButtonErrorShrinkFactor = 67.dp / pinButtonSize
diff --git a/packages/SystemUI/compose/testing/Android.bp b/packages/SystemUI/compose/testing/Android.bp
deleted file mode 100644
index 555f42e..0000000
--- a/packages/SystemUI/compose/testing/Android.bp
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2022 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 {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_library {
- name: "SystemUIComposeTesting",
- manifest: "AndroidManifest.xml",
-
- srcs: [
- "src/**/*.kt",
- ],
-
- static_libs: [
- "PlatformComposeCore",
- "SystemUIScreenshotLib",
-
- "androidx.compose.runtime_runtime",
- "androidx.compose.material3_material3",
- "androidx.compose.ui_ui-test-junit4",
- "androidx.compose.ui_ui-test-manifest",
- ],
-
- kotlincflags: ["-Xjvm-default=all"],
-}
diff --git a/packages/SystemUI/compose/testing/AndroidManifest.xml b/packages/SystemUI/compose/testing/AndroidManifest.xml
deleted file mode 100644
index b1f7c3b..0000000
--- a/packages/SystemUI/compose/testing/AndroidManifest.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2022 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="com.android.systemui.testing.compose">
- <application
- android:appComponentFactory="androidx.core.app.AppComponentFactory"
- tools:replace="android:appComponentFactory">
- </application>
-</manifest>
diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
deleted file mode 100644
index cb9e53c..0000000
--- a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2022 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.testing.compose
-
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.ViewRootForTest
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.test.onRoot
-import com.android.compose.theme.PlatformTheme
-import com.android.systemui.testing.screenshot.ScreenshotActivity
-import com.android.systemui.testing.screenshot.SystemUIGoldenImagePathManager
-import com.android.systemui.testing.screenshot.UnitTestBitmapMatcher
-import com.android.systemui.testing.screenshot.drawIntoBitmap
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import platform.test.screenshot.DeviceEmulationRule
-import platform.test.screenshot.DeviceEmulationSpec
-import platform.test.screenshot.MaterialYouColorsRule
-import platform.test.screenshot.ScreenshotTestRule
-import platform.test.screenshot.getEmulatedDevicePathConfig
-
-/** A rule for Compose screenshot diff tests. */
-class ComposeScreenshotTestRule(
- emulationSpec: DeviceEmulationSpec,
- assetPathRelativeToBuildRoot: String
-) : TestRule {
- private val colorsRule = MaterialYouColorsRule()
- private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
- private val screenshotRule =
- ScreenshotTestRule(
- SystemUIGoldenImagePathManager(
- getEmulatedDevicePathConfig(emulationSpec),
- assetPathRelativeToBuildRoot
- )
- )
- private val composeRule = createAndroidComposeRule<ScreenshotActivity>()
- private val delegateRule =
- RuleChain.outerRule(colorsRule)
- .around(deviceEmulationRule)
- .around(screenshotRule)
- .around(composeRule)
- private val matcher = UnitTestBitmapMatcher
-
- override fun apply(base: Statement, description: Description): Statement {
- return delegateRule.apply(base, description)
- }
-
- /**
- * Compare [content] with the golden image identified by [goldenIdentifier] in the context of
- * [testSpec].
- */
- fun screenshotTest(
- goldenIdentifier: String,
- content: @Composable () -> Unit,
- ) {
- // Make sure that the activity draws full screen and fits the whole display instead of the
- // system bars.
- val activity = composeRule.activity
- activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) }
-
- // Set the content using the AndroidComposeRule to make sure that the Activity is set up
- // correctly.
- composeRule.setContent {
- PlatformTheme {
- Surface(
- color = MaterialTheme.colorScheme.background,
- ) {
- content()
- }
- }
- }
- composeRule.waitForIdle()
-
- val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
- screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
- }
-}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index d208404..b9d6643 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -33,8 +33,8 @@
import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.TextAnimator
import com.android.systemui.customization.R
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
import java.io.PrintWriter
import java.util.Calendar
import java.util.Locale
@@ -51,7 +51,12 @@
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : TextView(context, attrs, defStyleAttr, defStyleRes) {
- var logBuffer: LogBuffer? = null
+ var messageBuffer: MessageBuffer? = null
+ set(value) {
+ logger = if (value != null) Logger(value, TAG) else null
+ }
+
+ private var logger: Logger? = null
private val time = Calendar.getInstance()
@@ -129,7 +134,7 @@
override fun onAttachedToWindow() {
super.onAttachedToWindow()
- logBuffer?.log(TAG, DEBUG, "onAttachedToWindow")
+ logger?.d("onAttachedToWindow")
refreshFormat()
}
@@ -145,39 +150,32 @@
time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
contentDescription = DateFormat.format(descFormat, time)
val formattedText = DateFormat.format(format, time)
- logBuffer?.log(TAG, DEBUG,
- { str1 = formattedText?.toString() },
- { "refreshTime: new formattedText=$str1" }
- )
+ logger?.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
// Setting text actually triggers a layout pass (because the text view is set to
// wrap_content width and TextView always relayouts for this). Avoid needless
// relayout if the text didn't actually change.
if (!TextUtils.equals(text, formattedText)) {
text = formattedText
- logBuffer?.log(TAG, DEBUG,
- { str1 = formattedText?.toString() },
- { "refreshTime: done setting new time text to: $str1" }
- )
+ logger?.d({ "refreshTime: done setting new time text to: $str1" }) {
+ str1 = formattedText?.toString()
+ }
// Because the TextLayout may mutate under the hood as a result of the new text, we
// notify the TextAnimator that it may have changed and request a measure/layout. A
// crash will occur on the next invocation of setTextStyle if the layout is mutated
// without being notified TextInterpolator being notified.
if (layout != null) {
textAnimator?.updateLayout(layout)
- logBuffer?.log(TAG, DEBUG, "refreshTime: done updating textAnimator layout")
+ logger?.d("refreshTime: done updating textAnimator layout")
}
requestLayout()
- logBuffer?.log(TAG, DEBUG, "refreshTime: after requestLayout")
+ logger?.d("refreshTime: after requestLayout")
}
}
fun onTimeZoneChanged(timeZone: TimeZone?) {
time.timeZone = timeZone
refreshFormat()
- logBuffer?.log(TAG, DEBUG,
- { str1 = timeZone?.toString() },
- { "onTimeZoneChanged newTimeZone=$str1" }
- )
+ logger?.d({ "onTimeZoneChanged newTimeZone=$str1" }) { str1 = timeZone?.toString() }
}
@SuppressLint("DrawAllocation")
@@ -191,7 +189,7 @@
} else {
animator.updateLayout(layout)
}
- logBuffer?.log(TAG, DEBUG, "onMeasure")
+ logger?.d("onMeasure")
}
override fun onDraw(canvas: Canvas) {
@@ -203,12 +201,12 @@
} else {
super.onDraw(canvas)
}
- logBuffer?.log(TAG, DEBUG, "onDraw")
+ logger?.d("onDraw")
}
override fun invalidate() {
super.invalidate()
- logBuffer?.log(TAG, DEBUG, "invalidate")
+ logger?.d("invalidate")
}
override fun onTextChanged(
@@ -218,10 +216,7 @@
lengthAfter: Int
) {
super.onTextChanged(text, start, lengthBefore, lengthAfter)
- logBuffer?.log(TAG, DEBUG,
- { str1 = text.toString() },
- { "onTextChanged text=$str1" }
- )
+ logger?.d({ "onTextChanged text=$str1" }) { str1 = text.toString() }
}
fun setLineSpacingScale(scale: Float) {
@@ -235,7 +230,7 @@
}
fun animateColorChange() {
- logBuffer?.log(TAG, DEBUG, "animateColorChange")
+ logger?.d("animateColorChange")
setTextStyle(
weight = lockScreenWeight,
textSize = -1f,
@@ -257,7 +252,7 @@
}
fun animateAppearOnLockscreen() {
- logBuffer?.log(TAG, DEBUG, "animateAppearOnLockscreen")
+ logger?.d("animateAppearOnLockscreen")
setTextStyle(
weight = dozingWeight,
textSize = -1f,
@@ -283,7 +278,7 @@
if (isAnimationEnabled && textAnimator == null) {
return
}
- logBuffer?.log(TAG, DEBUG, "animateFoldAppear")
+ logger?.d("animateFoldAppear")
setTextStyle(
weight = lockScreenWeightInternal,
textSize = -1f,
@@ -310,7 +305,7 @@
// Skip charge animation if dozing animation is already playing.
return
}
- logBuffer?.log(TAG, DEBUG, "animateCharge")
+ logger?.d("animateCharge")
val startAnimPhase2 = Runnable {
setTextStyle(
weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -334,7 +329,7 @@
}
fun animateDoze(isDozing: Boolean, animate: Boolean) {
- logBuffer?.log(TAG, DEBUG, "animateDoze")
+ logger?.d("animateDoze")
setTextStyle(
weight = if (isDozing) dozingWeight else lockScreenWeight,
textSize = -1f,
@@ -453,10 +448,7 @@
isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
else -> DOUBLE_LINE_FORMAT_12_HOUR
}
- logBuffer?.log(TAG, DEBUG,
- { str1 = format?.toString() },
- { "refreshFormat format=$str1" }
- )
+ logger?.d({ "refreshFormat format=$str1" }) { str1 = format?.toString() }
descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
refreshTime()
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 12f7452..d65edae 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -23,10 +23,11 @@
import android.provider.Settings
import android.util.Log
import androidx.annotation.OpenForTesting
-import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogMessageImpl
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.log.core.MessageInitializer
import com.android.systemui.log.core.MessagePrinter
import com.android.systemui.plugins.ClockController
@@ -75,7 +76,7 @@
private val TMP_MESSAGE: LogMessage by lazy { LogMessageImpl.Factory.create() }
-private inline fun LogBuffer?.tryLog(
+private inline fun Logger?.tryLog(
tag: String,
level: LogLevel,
messageInitializer: MessageInitializer,
@@ -84,7 +85,7 @@
) {
if (this != null) {
// Wrap messagePrinter to convert it from crossinline to noinline
- this.log(tag, level, messageInitializer, messagePrinter, ex)
+ this.log(level, messagePrinter, ex, messageInitializer)
} else {
messageInitializer(TMP_MESSAGE)
val msg = messagePrinter(TMP_MESSAGE)
@@ -110,7 +111,7 @@
val handleAllUsers: Boolean,
defaultClockProvider: ClockProvider,
val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
- val logBuffer: LogBuffer? = null,
+ messageBuffer: MessageBuffer? = null,
val keepAllLoaded: Boolean,
subTag: String,
var isTransitClockEnabled: Boolean = false,
@@ -124,6 +125,7 @@
fun onAvailableClocksChanged() {}
}
+ private val logger: Logger? = if (messageBuffer != null) Logger(messageBuffer, TAG) else null
private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
private val settingObserver =
@@ -150,7 +152,7 @@
val knownClocks = KNOWN_PLUGINS.get(manager.getPackage())
if (knownClocks == null) {
- logBuffer.tryLog(
+ logger.tryLog(
TAG,
LogLevel.WARNING,
{ str1 = manager.getPackage() },
@@ -159,7 +161,7 @@
return true
}
- logBuffer.tryLog(
+ logger.tryLog(
TAG,
LogLevel.INFO,
{ str1 = manager.getPackage() },
@@ -176,7 +178,7 @@
}
if (manager != info.manager) {
- logBuffer.tryLog(
+ logger.tryLog(
TAG,
LogLevel.ERROR,
{ str1 = id },
@@ -216,7 +218,7 @@
}
if (manager != info.manager) {
- logBuffer.tryLog(
+ logger.tryLog(
TAG,
LogLevel.ERROR,
{ str1 = id },
@@ -244,7 +246,7 @@
val id = clock.clockId
val info = availableClocks[id]
if (info?.manager != manager) {
- logBuffer.tryLog(
+ logger.tryLog(
TAG,
LogLevel.ERROR,
{ str1 = id },
@@ -319,7 +321,7 @@
ClockSettings.deserialize(json)
} catch (ex: Exception) {
- logBuffer.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex)
+ logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex)
null
}
settings = result
@@ -348,7 +350,7 @@
)
}
} catch (ex: Exception) {
- logBuffer.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex)
+ logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex)
}
settings = value
}
@@ -508,9 +510,9 @@
}
private fun onConnected(clockId: ClockId) {
- logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" })
+ logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" })
if (currentClockId == clockId) {
- logBuffer.tryLog(
+ logger.tryLog(
TAG,
LogLevel.INFO,
{ str1 = clockId },
@@ -520,10 +522,10 @@
}
private fun onLoaded(clockId: ClockId) {
- logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" })
+ logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" })
if (currentClockId == clockId) {
- logBuffer.tryLog(
+ logger.tryLog(
TAG,
LogLevel.INFO,
{ str1 = clockId },
@@ -534,10 +536,10 @@
}
private fun onUnloaded(clockId: ClockId) {
- logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" })
+ logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" })
if (currentClockId == clockId) {
- logBuffer.tryLog(
+ logger.tryLog(
TAG,
LogLevel.WARNING,
{ str1 = clockId },
@@ -548,10 +550,10 @@
}
private fun onDisconnected(clockId: ClockId) {
- logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" })
+ logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" })
if (currentClockId == clockId) {
- logBuffer.tryLog(
+ logger.tryLog(
TAG,
LogLevel.WARNING,
{ str1 = clockId },
@@ -597,22 +599,17 @@
if (isEnabled && clockId.isNotEmpty()) {
val clock = createClock(clockId)
if (clock != null) {
- logBuffer.tryLog(
- TAG,
- LogLevel.INFO,
- { str1 = clockId },
- { "Rendering clock $str1" }
- )
+ logger.tryLog(TAG, LogLevel.INFO, { str1 = clockId }, { "Rendering clock $str1" })
return clock
} else if (availableClocks.containsKey(clockId)) {
- logBuffer.tryLog(
+ logger.tryLog(
TAG,
LogLevel.WARNING,
{ str1 = clockId },
{ "Clock $str1 not loaded; using default" }
)
} else {
- logBuffer.tryLog(
+ logger.tryLog(
TAG,
LogLevel.ERROR,
{ str1 = clockId },
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index e557c8e..e539c95 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -24,7 +24,7 @@
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import com.android.systemui.customization.R
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.ClockAnimations
import com.android.systemui.plugins.ClockConfig
import com.android.systemui.plugins.ClockController
@@ -108,10 +108,10 @@
override val config = ClockFaceConfig()
- override var logBuffer: LogBuffer?
- get() = view.logBuffer
+ override var messageBuffer: MessageBuffer?
+ get() = view.messageBuffer
set(value) {
- view.logBuffer = value
+ view.messageBuffer = value
}
override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f)
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 1811c02..64c0f99 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -128,6 +128,16 @@
void setDozeAmount(float amount);
/**
+ * Set if dozing is true or false
+ */
+ default void setDozing(boolean dozing) {}
+
+ /**
+ * Set if split shade enabled
+ */
+ default void setSplitShadeEnabled(boolean enabled) {}
+
+ /**
* Set the current keyguard bypass enabled status.
*/
default void setKeyguardBypassEnabled(boolean enabled) {}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 537b7a4..d962732 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -18,7 +18,7 @@
import android.graphics.drawable.Drawable
import android.view.View
import com.android.internal.annotations.Keep
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.annotations.ProvidesInterface
import java.io.PrintWriter
import java.util.Locale
@@ -95,7 +95,7 @@
val animations: ClockAnimations
/** Some clocks may log debug information */
- var logBuffer: LogBuffer?
+ var messageBuffer: MessageBuffer?
}
/** Events that should call when various rendering parameters change */
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index 1f47e72..9bd26ab1 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -65,15 +65,7 @@
# The plugins, log & common subpackages act as shared libraries that might be referenced in
# dynamically-loaded plugin APKs.
-keep class com.android.systemui.plugins.** { *; }
--keep class com.android.systemui.log.ConstantStringsLoggerImpl { *; }
--keep class com.android.systemui.log.ConstantStringsLogger { *; }
--keep class com.android.systemui.log.LogBuffer { *; }
--keep class com.android.systemui.log.LogcatEchoTrackerDebug { *; }
--keep class com.android.systemui.log.LogcatEchoTracker { *; }
--keep class com.android.systemui.log.LogcatEchoTrackerProd { *; }
--keep class com.android.systemui.log.LogLevel { *; }
--keep class com.android.systemui.log.LogMessageImpl { *; }
--keep class com.android.systemui.log.LogMessage { *; }
+-keep class com.android.systemui.log.core.** { *; }
-keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
*;
}
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml b/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml
new file mode 100644
index 0000000..e572985
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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="M17,8H18C19.1,8 20,8.9 20,10V20C20,21.1 19.1,22 18,22H6C4.9,22 4,21.1 4,20V10C4,8.9 4.9,8 6,8H7V6C7,3.24 9.24,1 12,1C14.76,1 17,3.24 17,6V8ZM12,3C10.34,3 9,4.34 9,6V8H15V6C15,4.34 13.66,3 12,3ZM6,20V10H18V20H6ZM14,15C14,16.1 13.1,17 12,17C10.9,17 10,16.1 10,15C10,13.9 10.9,13 12,13C13.1,13 14,13.9 14,15Z"
+ android:fillColor="#5F6368"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml
new file mode 100644
index 0000000..360ef26
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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.android.systemui.biometrics.UdfpsKeyguardView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/udfps_animation_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- Add fingerprint views here. See udfps_keyguard_view_internal.xml. -->
+
+</com.android.systemui.biometrics.UdfpsKeyguardView>
diff --git a/packages/SystemUI/res/drawable/pin_dot_avd.xml b/packages/SystemUI/res/drawable/pin_dot_avd.xml
index 1c16251..710ba83 100644
--- a/packages/SystemUI/res/drawable/pin_dot_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_avd.xml
@@ -1 +1,40 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_0_G" android:translateX="28.237000000000002" android:translateY="23.112000000000002"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M-13.24 -12.11 C-11.03,-12.11 -9.24,-10.32 -9.24,-8.11 C-9.24,-5.9 -11.03,-4.11 -13.24,-4.11 C-15.44,-4.11 -17.24,-5.9 -17.24,-8.11 C-17.24,-10.32 -15.44,-12.11 -13.24,-12.11c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:height="30dp"
+ android:width="30dp"
+ android:viewportHeight="30"
+ android:viewportWidth="30">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="28.237000000000002"
+ android:translateY="23.112000000000002">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:strokeColor="#ffffff"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M-13.24 -12.11 C-11.03,-12.11 -9.24,-10.32 -9.24,-8.11 C-9.24,-5.9 -11.03,-4.11 -13.24,-4.11 C-15.44,-4.11 -17.24,-5.9 -17.24,-8.11 C-17.24,-10.32 -15.44,-12.11 -13.24,-12.11c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="translateX"
+ android:duration="500"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml b/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml
index 0f8703f..72a03bf 100644
--- a/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml
@@ -1 +1,130 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_0_G" android:translateX="28.54" android:translateY="23.54"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0" android:strokeAlpha="1" android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="150" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="200" android:startOffset="150" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="150" android:startOffset="0" android:valueFrom="M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " android:valueTo="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="200" android:startOffset="150" android:valueFrom="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueTo="M-13.54 -12.54 C-11.33,-12.54 -9.54,-10.75 -9.54,-8.54 C-9.54,-6.33 -11.33,-4.54 -13.54,-4.54 C-15.75,-4.54 -17.54,-6.33 -17.54,-8.54 C-17.54,-10.75 -15.75,-12.54 -13.54,-12.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="150" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeWidth" android:duration="50" android:startOffset="150" android:valueFrom="0" android:valueTo="2" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="150" android:startOffset="0" android:valueFrom="M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " android:valueTo="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="200" android:startOffset="150" android:valueFrom="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueTo="M-13.54 -12.54 C-11.33,-12.54 -9.54,-10.75 -9.54,-8.54 C-9.54,-6.33 -11.33,-4.54 -13.54,-4.54 C-15.75,-4.54 -17.54,-6.33 -17.54,-8.54 C-17.54,-10.75 -15.75,-12.54 -13.54,-12.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_1_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="150"
+ android:propertyName="scaleX"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="0.43"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="150"
+ android:propertyName="scaleY"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="0.43"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="fillAlpha"
+ android:duration="200"
+ android:startOffset="150"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="200"
+ android:propertyName="scaleX"
+ android:startOffset="150"
+ android:valueFrom="0.65"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="200"
+ android:propertyName="scaleY"
+ android:startOffset="150"
+ android:valueFrom="0.65"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="500"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="30dp"
+ android:height="30dp"
+ android:viewportHeight="30"
+ android:viewportWidth="30">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:pivotX="-13.54"
+ android:pivotY="-8.54"
+ android:scaleX="1"
+ android:scaleY="1"
+ android:translateX="28.54"
+ android:translateY="23.54">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:pivotX="317.509"
+ android:pivotY="-826.986"
+ android:scaleX="0.65"
+ android:scaleY="0.65"
+ android:translateX="-302.509"
+ android:translateY="841.986">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M317.51 -821.99 C314.75,-821.99 312.51,-824.23 312.51,-826.99 C312.51,-829.74 314.75,-831.99 317.51,-831.99 C320.27,-831.99 322.51,-829.74 322.51,-826.99 C322.51,-824.23 320.27,-821.99 317.51,-821.99z M317.51 -829.99 C315.86,-829.99 314.51,-828.64 314.51,-826.99 C314.51,-825.33 315.86,-823.99 317.51,-823.99 C319.16,-823.99 320.51,-825.33 320.51,-826.99 C320.51,-828.64 319.16,-829.99 317.51,-829.99z " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml
index f1fb2aa..02abb99 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml
@@ -1 +1,141 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-13.65 -3.95 C-16.31,-10.09 -10.09,-16.31 -3.95,-13.65 C-3.95,-13.65 -2.94,-13.21 -2.94,-13.21 C-1.06,-12.39 1.06,-12.39 2.94,-13.21 C2.94,-13.21 3.95,-13.65 3.95,-13.65 C10.09,-16.31 16.31,-10.09 13.65,-3.95 C13.65,-3.95 13.21,-2.94 13.21,-2.94 C12.39,-1.06 12.39,1.06 13.21,2.94 C13.21,2.94 13.65,3.95 13.65,3.95 C16.31,10.09 10.09,16.31 3.95,13.65 C3.95,13.65 2.94,13.21 2.94,13.21 C1.06,12.39 -1.06,12.39 -2.94,13.21 C-2.94,13.21 -3.95,13.65 -3.95,13.65 C-10.09,16.31 -16.31,10.09 -13.65,3.95 C-13.65,3.95 -13.21,2.94 -13.21,2.94 C-12.39,1.06 -12.39,-1.06 -13.21,-2.94 C-13.21,-2.94 -13.65,-3.95 -13.65,-3.95c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueTo="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.3" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.3" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="67"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "
+ android:valueTo="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="283"
+ android:propertyName="pathData"
+ android:startOffset="67"
+ android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "
+ android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="0"
+ android:propertyName="scaleY"
+ android:startOffset="67"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="67"
+ android:propertyName="scaleX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="67"
+ android:propertyName="scaleY"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="283"
+ android:propertyName="scaleX"
+ android:startOffset="67"
+ android:valueFrom="1"
+ android:valueTo="0.3"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="283"
+ android:propertyName="scaleY"
+ android:startOffset="67"
+ android:valueFrom="1"
+ android:valueTo="0.3"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="500"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="30dp"
+ android:height="30dp"
+ android:viewportHeight="30"
+ android:viewportWidth="30">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:scaleY="0"
+ android:translateX="15.441"
+ android:translateY="15.691">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="15"
+ android:translateY="15">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-13.65 -3.95 C-16.31,-10.09 -10.09,-16.31 -3.95,-13.65 C-3.95,-13.65 -2.94,-13.21 -2.94,-13.21 C-1.06,-12.39 1.06,-12.39 2.94,-13.21 C2.94,-13.21 3.95,-13.65 3.95,-13.65 C10.09,-16.31 16.31,-10.09 13.65,-3.95 C13.65,-3.95 13.21,-2.94 13.21,-2.94 C12.39,-1.06 12.39,1.06 13.21,2.94 C13.21,2.94 13.65,3.95 13.65,3.95 C16.31,10.09 10.09,16.31 3.95,13.65 C3.95,13.65 2.94,13.21 2.94,13.21 C1.06,12.39 -1.06,12.39 -2.94,13.21 C-2.94,13.21 -3.95,13.65 -3.95,13.65 C-10.09,16.31 -16.31,10.09 -13.65,3.95 C-13.65,3.95 -13.21,2.94 -13.21,2.94 C-12.39,1.06 -12.39,-1.06 -13.21,-2.94 C-13.21,-2.94 -13.65,-3.95 -13.65,-3.95c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml
index 3717db8..1607327 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml
@@ -1 +1,148 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="13.205" android:pivotY="1.795" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueTo="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueTo="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueTo="M6.12 0.21 C6.12,0.21 2.27,-4.35 2.27,-4.35 C1.34,-6.2 -1.3,-6.2 -2.23,-4.35 C-2.23,-4.35 -6.06,0.21 -6.06,0.21 C-7.12,2.33 -5.46,5.79 -4.03,6.54 C-2.28,7.45 -1.01,7.48 -1.01,7.48 C-1.01,7.48 1.05,7.48 1.05,7.48 C1.05,7.48 3.28,7.36 4.23,6.66 C4.92,6.15 7.18,2.33 6.12,0.21c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:height="30dp"
+ android:width="30dp"
+ android:viewportHeight="30"
+ android:viewportWidth="30">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateX="15.397"
+ android:translateY="15.691"
+ android:scaleY="0">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillColor="#ffffff"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="15"
+ android:translateY="13.205"
+ android:pivotY="1.795">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillColor="#ffffff"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="67"
+ android:startOffset="0"
+ android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "
+ android:valueTo="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="283"
+ android:startOffset="67"
+ android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "
+ android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="0"
+ android:startOffset="67"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="67"
+ android:startOffset="0"
+ android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "
+ android:valueTo="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="283"
+ android:startOffset="67"
+ android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "
+ android:valueTo="M6.12 0.21 C6.12,0.21 2.27,-4.35 2.27,-4.35 C1.34,-6.2 -1.3,-6.2 -2.23,-4.35 C-2.23,-4.35 -6.06,0.21 -6.06,0.21 C-7.12,2.33 -5.46,5.79 -4.03,6.54 C-2.28,7.45 -1.01,7.48 -1.01,7.48 C-1.01,7.48 1.05,7.48 1.05,7.48 C1.05,7.48 3.28,7.36 4.23,6.66 C4.92,6.15 7.18,2.33 6.12,0.21c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="scaleX"
+ android:duration="67"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="67"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="translateX"
+ android:duration="500"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml
index 95b8044..78e2249 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml
@@ -1 +1,141 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M10.71 10.71 C5.92,15.5 -1.85,15.5 -6.64,10.71 C-6.64,10.71 -10.71,6.64 -10.71,6.64 C-15.5,1.85 -15.5,-5.92 -10.71,-10.71 C-5.92,-15.5 1.85,-15.5 6.64,-10.71 C6.64,-10.71 10.71,-6.64 10.71,-6.64 C15.5,-1.85 15.5,5.92 10.71,10.71c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueTo="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.4" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.4" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:height="30dp"
+ android:width="30dp"
+ android:viewportHeight="30"
+ android:viewportWidth="30">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateX="15.441"
+ android:translateY="15.691"
+ android:scaleY="0">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillColor="#ffffff"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="15"
+ android:translateY="15">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillColor="#ffffff"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M10.71 10.71 C5.92,15.5 -1.85,15.5 -6.64,10.71 C-6.64,10.71 -10.71,6.64 -10.71,6.64 C-15.5,1.85 -15.5,-5.92 -10.71,-10.71 C-5.92,-15.5 1.85,-15.5 6.64,-10.71 C6.64,-10.71 10.71,-6.64 10.71,-6.64 C15.5,-1.85 15.5,5.92 10.71,10.71c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="67"
+ android:startOffset="0"
+ android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "
+ android:valueTo="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="283"
+ android:startOffset="67"
+ android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "
+ android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="0"
+ android:startOffset="67"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="scaleX"
+ android:duration="67"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="67"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleX"
+ android:duration="283"
+ android:startOffset="67"
+ android:valueFrom="1"
+ android:valueTo="0.4"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="283"
+ android:startOffset="67"
+ android:valueFrom="1"
+ android:valueTo="0.4"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="translateX"
+ android:duration="500"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml
index 8ea8f85..35c7210 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml
@@ -1 +1,119 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.44 -8.69 C3.97,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.72 3.97,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.72 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c " android:valueTo="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c " android:valueTo="M7.73 0 C7.73,-1.3 7.71,-1.88 7.16,-2.92 C6.62,-3.95 6.39,-4.63 5.47,-5.47 C4.55,-6.31 4.06,-6.74 3.04,-7.11 C2.02,-7.48 1.35,-7.73 0,-7.73 C-1.34,-7.73 -2.1,-7.52 -3.02,-7.12 C-3.93,-6.73 -4.52,-6.41 -5.47,-5.47 C-6.42,-4.53 -6.75,-3.96 -7.12,-3.02 C-7.52,-2.01 -7.73,-1.35 -7.73,0 C-7.73,1.35 -7.6,1.91 -7.22,2.77 C-6.85,3.63 -6.46,4.51 -5.47,5.47 C-4.48,6.43 -3.87,6.73 -3.02,7.12 C-2.17,7.52 -1.28,7.73 0,7.73 C1.28,7.73 1.8,7.62 2.81,7.21 C3.81,6.8 4.69,6.41 5.47,5.47 C6.25,4.53 6.55,4.3 7.05,3.19 C7.55,2.07 7.73,1.3 7.73,0c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_1_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="0"
+ android:propertyName="scaleY"
+ android:startOffset="67"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="67"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c "
+ android:valueTo="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="283"
+ android:propertyName="pathData"
+ android:startOffset="67"
+ android:valueFrom="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c "
+ android:valueTo="M7.73 0 C7.73,-1.3 7.71,-1.88 7.16,-2.92 C6.62,-3.95 6.39,-4.63 5.47,-5.47 C4.55,-6.31 4.06,-6.74 3.04,-7.11 C2.02,-7.48 1.35,-7.73 0,-7.73 C-1.34,-7.73 -2.1,-7.52 -3.02,-7.12 C-3.93,-6.73 -4.52,-6.41 -5.47,-5.47 C-6.42,-4.53 -6.75,-3.96 -7.12,-3.02 C-7.52,-2.01 -7.73,-1.35 -7.73,0 C-7.73,1.35 -7.6,1.91 -7.22,2.77 C-6.85,3.63 -6.46,4.51 -5.47,5.47 C-4.48,6.43 -3.87,6.73 -3.02,7.12 C-2.17,7.52 -1.28,7.73 0,7.73 C1.28,7.73 1.8,7.62 2.81,7.21 C3.81,6.8 4.69,6.41 5.47,5.47 C6.25,4.53 6.55,4.3 7.05,3.19 C7.55,2.07 7.73,1.3 7.73,0c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="67"
+ android:propertyName="scaleX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="67"
+ android:propertyName="scaleY"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="500"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="30dp"
+ android:height="30dp"
+ android:viewportHeight="30"
+ android:viewportWidth="30">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:scaleY="0"
+ android:translateX="15.441"
+ android:translateY="15.691">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-0.44 -8.69 C3.97,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.72 3.97,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.72 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="15"
+ android:translateY="15">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml
index 3779c80..9458b2e 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml
@@ -1 +1,148 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="17.64" android:pivotY="-2.64" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueTo="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueTo="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueTo="M-2.99 -8.52 C-1.33,-9.83 1.52,-9.34 3.25,-8.28 C4.98,-7.22 6.77,-5.88 6.7,-2.4 C6.64,1.08 4.48,2.26 3.2,3.05 C1.92,3.83 -1.1,3.72 -3.03,2.76 C-4.97,1.79 -6.38,0.3 -6.37,-2.59 C-6.36,-5.49 -4.65,-7.2 -2.99,-8.52c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:height="30dp"
+ android:width="30dp"
+ android:viewportHeight="30"
+ android:viewportWidth="30">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateX="15.397"
+ android:translateY="15.691"
+ android:scaleY="0">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillColor="#ffffff"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="15"
+ android:translateY="17.64"
+ android:pivotY="-2.64">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillColor="#ffffff"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="67"
+ android:startOffset="0"
+ android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "
+ android:valueTo="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="283"
+ android:startOffset="67"
+ android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "
+ android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="0"
+ android:startOffset="67"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="67"
+ android:startOffset="0"
+ android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "
+ android:valueTo="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="283"
+ android:startOffset="67"
+ android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "
+ android:valueTo="M-2.99 -8.52 C-1.33,-9.83 1.52,-9.34 3.25,-8.28 C4.98,-7.22 6.77,-5.88 6.7,-2.4 C6.64,1.08 4.48,2.26 3.2,3.05 C1.92,3.83 -1.1,3.72 -3.03,2.76 C-4.97,1.79 -6.38,0.3 -6.37,-2.59 C-6.36,-5.49 -4.65,-7.2 -2.99,-8.52c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="scaleX"
+ android:duration="67"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="67"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="translateX"
+ android:duration="500"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml
index ddda94a..06e27df 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml
@@ -1 +1,142 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15.859" android:pivotY="-0.859" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-2.82 -14.07 C-1.14,-15.29 1.14,-15.29 2.82,-14.07 C2.82,-14.07 7.73,-10.53 7.73,-10.53 C7.73,-10.53 12.58,-7 12.58,-7 C14.29,-5.76 15,-3.55 14.35,-1.54 C14.35,-1.54 12.51,4.14 12.51,4.14 C12.51,4.14 10.64,9.85 10.64,9.85 C9.99,11.84 8.14,13.19 6.05,13.19 C6.05,13.19 0,13.2 0,13.2 C0,13.2 -6.05,13.19 -6.05,13.19 C-8.14,13.19 -9.98,11.84 -10.64,9.85 C-10.64,9.85 -12.51,4.14 -12.51,4.14 C-12.51,4.14 -14.35,-1.54 -14.35,-1.54 C-15,-3.55 -14.29,-5.76 -12.58,-7 C-12.58,-7 -7.73,-10.53 -7.73,-10.53 C-7.73,-10.53 -2.82,-14.07 -2.82,-14.07c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueTo="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.35000000000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.35000000000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:height="30dp"
+ android:width="30dp"
+ android:viewportHeight="30"
+ android:viewportWidth="30">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateX="15.397"
+ android:translateY="15.691"
+ android:scaleY="0">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillColor="#ffffff"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="15"
+ android:translateY="15.859"
+ android:pivotY="-0.859">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillColor="#ffffff"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M-2.82 -14.07 C-1.14,-15.29 1.14,-15.29 2.82,-14.07 C2.82,-14.07 7.73,-10.53 7.73,-10.53 C7.73,-10.53 12.58,-7 12.58,-7 C14.29,-5.76 15,-3.55 14.35,-1.54 C14.35,-1.54 12.51,4.14 12.51,4.14 C12.51,4.14 10.64,9.85 10.64,9.85 C9.99,11.84 8.14,13.19 6.05,13.19 C6.05,13.19 0,13.2 0,13.2 C0,13.2 -6.05,13.19 -6.05,13.19 C-8.14,13.19 -9.98,11.84 -10.64,9.85 C-10.64,9.85 -12.51,4.14 -12.51,4.14 C-12.51,4.14 -14.35,-1.54 -14.35,-1.54 C-15,-3.55 -14.29,-5.76 -12.58,-7 C-12.58,-7 -7.73,-10.53 -7.73,-10.53 C-7.73,-10.53 -2.82,-14.07 -2.82,-14.07c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="67"
+ android:startOffset="0"
+ android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "
+ android:valueTo="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="pathData"
+ android:duration="283"
+ android:startOffset="67"
+ android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "
+ android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="0"
+ android:startOffset="67"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="scaleX"
+ android:duration="67"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="67"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleX"
+ android:duration="283"
+ android:startOffset="67"
+ android:valueFrom="1"
+ android:valueTo="0.35000000000000003"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="283"
+ android:startOffset="67"
+ android:valueFrom="1"
+ android:valueTo="0.35000000000000003"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="translateX"
+ android:duration="500"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 50dcaf3..bb32022 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -57,7 +57,6 @@
android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
- android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
android:src="@drawable/ic_alarm"
android:tint="@android:color/white"
android:visibility="gone"
@@ -68,7 +67,6 @@
android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
- android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
android:src="@drawable/ic_qs_dnd_on"
android:tint="@android:color/white"
android:visibility="gone"
@@ -79,7 +77,6 @@
android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
- android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop"
android:src="@drawable/ic_signal_wifi_off"
android:visibility="gone"
android:contentDescription="@string/dream_overlay_status_bar_wifi_off" />
diff --git a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
index 7105721..21e0d2c 100644
--- a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
+++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
@@ -164,7 +164,6 @@
/>
<ImageView
android:id="@+id/media_output_item_end_click_icon"
- android:src="@drawable/media_output_status_edit_session"
android:layout_width="24dp"
android:layout_height="24dp"
android:focusable="false"
diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml
index 6cc72dd..d9f4b79 100644
--- a/packages/SystemUI/res/layout/screen_record_options.xml
+++ b/packages/SystemUI/res/layout/screen_record_options.xml
@@ -55,14 +55,13 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:gravity="center_vertical"
android:layout_marginTop="@dimen/screenrecord_option_padding">
<ImageView
android:layout_width="@dimen/screenrecord_option_icon_size"
android:layout_height="@dimen/screenrecord_option_icon_size"
- android:layout_weight="0"
android:src="@drawable/ic_touch"
android:tint="?android:attr/textColorSecondary"
- android:layout_gravity="center_vertical"
android:layout_marginRight="@dimen/screenrecord_option_padding"
android:importantForAccessibility="no"/>
<TextView
@@ -70,7 +69,6 @@
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_weight="1"
- android:gravity="center_vertical"
android:text="@string/screenrecord_taps_label"
android:textAppearance="?android:attr/textAppearanceMedium"
android:fontFamily="@*android:string/config_bodyFontFamily"
@@ -80,7 +78,6 @@
android:layout_width="wrap_content"
android:minWidth="48dp"
android:layout_height="48dp"
- android:layout_weight="0"
android:id="@+id/screenrecord_taps_switch"
style="@style/ScreenRecord.Switch"
android:importantForAccessibility="yes"/>
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
index 00af7f4..530d752 100644
--- a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
+++ b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
@@ -16,8 +16,7 @@
-->
<com.android.systemui.biometrics.UdfpsKeyguardViewLegacy
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/udfps_animation_view"
+ android:id="@+id/udfps_animation_view_legacy"
android:layout_width="match_parent"
android:layout_height="match_parent">
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 59becc6..9a2db6bfd 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -20,6 +20,13 @@
On large screens should be the same as the regular status bar. -->
<dimen name="status_bar_header_height_keyguard">@dimen/status_bar_height</dimen>
+ <!-- padding for container with status icons and battery -->
+ <dimen name="status_bar_icons_padding_end">12dp</dimen>
+ <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
+ <dimen name="status_bar_icons_padding_start">11dp</dimen>
+
+ <dimen name="status_bar_padding_end">0dp</dimen>
+
<!-- Size of user icon + frame in the qs user picker (incl. frame) -->
<dimen name="qs_framed_avatar_size">60dp</dimen>
<!-- Size of user icon + frame in the keyguard user picker (incl. frame) -->
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index d277dae..d053a7a 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -16,13 +16,9 @@
*/
-->
<resources>
- <!-- padding for container with status icons and battery -->
- <dimen name="status_bar_icons_padding_end">12dp</dimen>
- <!-- it's a bit smaller on large screen to account for status_bar_icon_horizontal_margin -->
+ <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin -->
<dimen name="status_bar_icons_padding_start">10dp</dimen>
- <dimen name="status_bar_padding_end">0dp</dimen>
-
<!-- gap on either side of status bar notification icons -->
<dimen name="status_bar_icon_horizontal_margin">1dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3bd7a06..d9466bf 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1793,7 +1793,6 @@
<dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen>
<dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen>
<dimen name="dream_overlay_icon_inset_dimen">0dp</dimen>
- <dimen name="dream_overlay_status_bar_marginTop">22dp</dimen>
<!-- Default device corner radius, used for assist UI -->
<dimen name="config_rounded_mask_size">0px</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index fd74c7e..6b85621 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -398,7 +398,8 @@
<item name="android:itemTextAppearance">@style/Control.MenuItem</item>
</style>
- <style name="Theme.SystemUI.QuickSettings.BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
+ <!-- Cannot double inherit. Use Theme.SystemUI.QuickSettings in code to match -->
+ <style name="BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
<item name="android:windowBackground">@android:color/transparent</item>
</style>
diff --git a/packages/SystemUI/screenshot/Android.bp b/packages/SystemUI/screenshot/Android.bp
deleted file mode 100644
index f449398..0000000
--- a/packages/SystemUI/screenshot/Android.bp
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (C) 2022 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 {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_library {
- name: "SystemUIScreenshotLib",
- manifest: "AndroidManifest.xml",
-
- srcs: [
- "src/**/*.kt",
- ],
-
- resource_dirs: [
- "res",
- ],
-
- static_libs: [
- "SystemUI-core",
- "androidx.test.espresso.core",
- "androidx.appcompat_appcompat",
- "platform-screenshot-diff-core",
- "guava",
- ],
-
- kotlincflags: ["-Xjvm-default=all"],
-}
diff --git a/packages/SystemUI/screenshot/AndroidManifest.xml b/packages/SystemUI/screenshot/AndroidManifest.xml
deleted file mode 100644
index ba3dc8c..0000000
--- a/packages/SystemUI/screenshot/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2022 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.systemui.testing.screenshot">
- <application>
- <activity
- android:name="com.android.systemui.testing.screenshot.ScreenshotActivity"
- android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
- android:exported="true"
- android:theme="@style/Theme.SystemUI.Screenshot" />
- </application>
-</manifest>
diff --git a/packages/SystemUI/screenshot/res/values/themes.xml b/packages/SystemUI/screenshot/res/values/themes.xml
deleted file mode 100644
index a7f8a26..0000000
--- a/packages/SystemUI/screenshot/res/values/themes.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2022 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>
- <style name="Theme.SystemUI.Screenshot" parent="Theme.SystemUI">
- <item name="android:windowActionBar">false</item>
- <item name="android:windowNoTitle">true</item>
-
- <!-- We make the status and navigation bars transparent so that the screenshotted content is
- not clipped by the status bar height when drawn into the Bitmap (which is what happens
- given that we draw the view into the Bitmap using hardware acceleration). -->
- <item name="android:statusBarColor">@android:color/transparent</item>
- <item name="android:navigationBarColor">@android:color/transparent</item>
-
- <!-- Make sure that device specific cutouts don't impact the outcome of screenshot tests -->
- <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
- </style>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt
deleted file mode 100644
index a4a70a4..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2022 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.testing.screenshot
-
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.os.Build
-import android.view.View
-import platform.test.screenshot.matchers.MSSIMMatcher
-import platform.test.screenshot.matchers.PixelPerfectMatcher
-
-/** Draw this [View] into a [Bitmap]. */
-// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their
-// tests.
-fun View.drawIntoBitmap(): Bitmap {
- val bitmap =
- Bitmap.createBitmap(
- measuredWidth,
- measuredHeight,
- Bitmap.Config.ARGB_8888,
- )
- val canvas = Canvas(bitmap)
- draw(canvas)
- return bitmap
-}
-
-/**
- * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
- * screenshot *unit* tests.
- */
-val UnitTestBitmapMatcher =
- if (Build.CPU_ABI == "x86_64") {
- // Different CPU architectures can sometimes end up rendering differently, so we can't do
- // pixel-perfect matching on different architectures using the same golden. Given that our
- // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the
- // x86_64 architecture and use the Structural Similarity Index on others.
- // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can
- // do pixel perfect matching both at presubmit time and at development time with actual
- // devices.
- PixelPerfectMatcher()
- } else {
- MSSIMMatcher()
- }
-
-/**
- * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
- * screenshot *unit* tests.
- *
- * We use the Structural Similarity Index for integration tests because they usually contain
- * additional information and noise that shouldn't break the test.
- */
-val IntegrationTestBitmapMatcher = MSSIMMatcher()
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
deleted file mode 100644
index e032bb9..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2022 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.testing.screenshot
-
-import android.app.Activity
-import android.graphics.Color
-import android.view.View
-import android.view.Window
-import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
-import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.WindowInsetsControllerCompat
-import androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
-import androidx.test.platform.app.InstrumentationRegistry
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import platform.test.screenshot.*
-
-/**
- * A rule that allows to run a screenshot diff test on a view that is hosted in another activity.
- */
-class ExternalViewScreenshotTestRule(
- emulationSpec: DeviceEmulationSpec,
- assetPathRelativeToBuildRoot: String
-) : TestRule {
-
- private val colorsRule = MaterialYouColorsRule()
- private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
- private val screenshotRule =
- ScreenshotTestRule(
- SystemUIGoldenImagePathManager(
- getEmulatedDevicePathConfig(emulationSpec),
- assetPathRelativeToBuildRoot
- )
- )
- private val delegateRule =
- RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule)
- private val matcher = UnitTestBitmapMatcher
-
- override fun apply(base: Statement, description: Description): Statement {
- return delegateRule.apply(base, description)
- }
-
- /**
- * Compare the content of the [view] with the golden image identified by [goldenIdentifier] in
- * the context of [emulationSpec]. Window must be specified to capture views that render
- * hardware buffers.
- */
- fun screenshotTest(goldenIdentifier: String, view: View, window: Window? = null) {
- view.removeElevationRecursively()
-
- ScreenshotRuleAsserter.Builder(screenshotRule)
- .setScreenshotProvider { view.toBitmap(window) }
- .withMatcher(matcher)
- .build()
- .assertGoldenImage(goldenIdentifier)
- }
-
- /**
- * Compare the content of the [activity] with the golden image identified by [goldenIdentifier]
- * in the context of [emulationSpec].
- */
- fun activityScreenshotTest(
- goldenIdentifier: String,
- activity: Activity,
- ) {
- val rootView = activity.window.decorView
-
- // Hide system bars, remove insets, focus and make sure device-specific cutouts
- // don't affect screenshots
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- val window = activity.window
- window.setDecorFitsSystemWindows(false)
- WindowInsetsControllerCompat(window, rootView).apply {
- hide(WindowInsetsCompat.Type.systemBars())
- systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
- }
-
- window.statusBarColor = Color.TRANSPARENT
- window.navigationBarColor = Color.TRANSPARENT
- window.attributes =
- window.attributes.apply {
- layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
- }
-
- rootView.removeInsetsRecursively()
- activity.currentFocus?.clearFocus()
- }
-
- screenshotTest(goldenIdentifier, rootView, activity.window)
- }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
deleted file mode 100644
index 72d8c5a..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2022 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.testing.screenshot
-
-import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
-import platform.test.screenshot.PathConfig
-
-/** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */
-class SystemUIGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String
-) :
- GoldenImagePathManager(
- appContext = InstrumentationRegistry.getInstrumentation().context,
- assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
- deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
- .targetContext
- .filesDir
- .absolutePath
- .toString() + "/sysui_screenshots",
- pathConfig = pathConfig,
- ) {
- override fun toString(): String {
- // This string is appended to all actual/expected screenshots on the device, so make sure
- // it is a static value.
- return "SystemUIGoldenImagePathManager"
- }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt
deleted file mode 100644
index 98e9aaf..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2022 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.testing.screenshot
-
-import android.app.Activity
-import android.content.Intent
-import androidx.core.app.AppComponentFactory
-
-class TestAppComponentFactory : AppComponentFactory() {
-
- init {
- instance = this
- }
-
- private val overrides: MutableMap<String, () -> Activity> = hashMapOf()
-
- fun clearOverrides() {
- overrides.clear()
- }
-
- fun <T : Activity> registerActivityOverride(activity: Class<T>, provider: () -> T) {
- overrides[activity.name] = provider
- }
-
- override fun instantiateActivityCompat(
- cl: ClassLoader,
- className: String,
- intent: Intent?
- ): Activity {
- return overrides
- .getOrDefault(className) { super.instantiateActivityCompat(cl, className, intent) }
- .invoke()
- }
-
- companion object {
-
- private var instance: TestAppComponentFactory? = null
-
- fun getInstance(): TestAppComponentFactory =
- instance
- ?: error(
- "TestAppComponentFactory is not initialized, " +
- "did you specify it in the manifest?"
- )
- }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
deleted file mode 100644
index b84d26a..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2022 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.testing.screenshot
-
-import android.view.View
-import android.view.ViewGroup
-import com.android.systemui.util.children
-import android.view.WindowInsets
-
-/**
- * Elevation/shadows is not deterministic when doing hardware rendering, this exentsion allows to
- * disable it for any view in the hierarchy.
- */
-fun View.removeElevationRecursively() {
- this.elevation = 0f
- (this as? ViewGroup)?.children?.forEach(View::removeElevationRecursively)
-}
-
-/**
- * Different devices could have different insets (e.g. different height of the navigation bar or
- * taskbar). This method dispatches empty insets to the whole view hierarchy and removes
- * the original listener, so the views won't receive real insets.
- */
-fun View.removeInsetsRecursively() {
- this.dispatchApplyWindowInsets(WindowInsets.CONSUMED)
- this.setOnApplyWindowInsetsListener(null)
- (this as? ViewGroup)?.children?.forEach(View::removeInsetsRecursively)
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
deleted file mode 100644
index dedf0a7..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
+++ /dev/null
@@ -1,233 +0,0 @@
-package com.android.systemui.testing.screenshot
-
-import android.annotation.WorkerThread
-import android.app.Activity
-import android.content.Context
-import android.content.ContextWrapper
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.HardwareRenderer
-import android.graphics.Rect
-import android.os.Build
-import android.os.Handler
-import android.os.Looper
-import android.util.Log
-import android.view.PixelCopy
-import android.view.SurfaceView
-import android.view.View
-import android.view.ViewTreeObserver
-import android.view.Window
-import androidx.annotation.RequiresApi
-import androidx.concurrent.futures.ResolvableFuture
-import androidx.test.annotation.ExperimentalTestApi
-import androidx.test.core.internal.os.HandlerExecutor
-import androidx.test.espresso.Espresso
-import androidx.test.platform.graphics.HardwareRendererCompat
-import com.google.common.util.concurrent.FutureCallback
-import com.google.common.util.concurrent.Futures
-import com.google.common.util.concurrent.ListenableFuture
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.runBlocking
-
-/*
- * This file was forked from androidx/test/core/view/ViewCapture.kt to add [Window] parameter to
- * [View.captureToBitmap].
- * TODO(b/195673633): Remove this fork and use the AndroidX version instead.
- */
-
-/**
- * Asynchronously captures an image of the underlying view into a [Bitmap].
- *
- * For devices below [Build.VERSION_CODES#O] (or if the view's window cannot be determined), the
- * image is obtained using [View#draw]. Otherwise, [PixelCopy] is used.
- *
- * This method will also enable [HardwareRendererCompat#setDrawingEnabled(boolean)] if required.
- *
- * This API is primarily intended for use in lower layer libraries or frameworks. For test authors,
- * its recommended to use espresso or compose's captureToImage.
- *
- * This API is currently experimental and subject to change or removal.
- */
-@ExperimentalTestApi
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> {
- val bitmapFuture: ResolvableFuture<Bitmap> = ResolvableFuture.create()
- val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper()))
- val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
-
- // disable drawing again if necessary once work is complete
- if (!HardwareRendererCompat.isDrawingEnabled()) {
- HardwareRendererCompat.setDrawingEnabled(true)
- bitmapFuture.addListener({ HardwareRendererCompat.setDrawingEnabled(false) }, mainExecutor)
- }
-
- mainExecutor.execute {
- if (isRobolectric) {
- generateBitmap(bitmapFuture)
- } else {
- val forceRedrawFuture = forceRedraw()
- forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor)
- }
- }
-
- return bitmapFuture
-}
-
-/**
- * Synchronously captures an image of the view into a [Bitmap]. Synchronous equivalent of
- * [captureToBitmap].
- */
-@WorkerThread
-@ExperimentalTestApi
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-fun View.toBitmap(window: Window? = null): Bitmap {
- if (Looper.getMainLooper() == Looper.myLooper()) {
- error("toBitmap() can't be called from the main thread")
- }
-
- if (!HardwareRenderer.isDrawingEnabled()) {
- error("Hardware rendering is not enabled")
- }
-
- // Make sure we are idle.
- Espresso.onIdle()
-
- val mainExecutor = context.mainExecutor
- return runBlocking {
- suspendCoroutine { continuation ->
- Futures.addCallback(
- captureToBitmap(window),
- object : FutureCallback<Bitmap> {
- override fun onSuccess(result: Bitmap?) {
- continuation.resumeWith(Result.success(result!!))
- }
-
- override fun onFailure(t: Throwable) {
- continuation.resumeWith(Result.failure(t))
- }
- },
- // We know that we are not on the main thread, so we can block the current
- // thread and wait for the result in the main thread.
- mainExecutor,
- )
- }
- }
-}
-
-/**
- * Trigger a redraw of the given view.
- *
- * Should only be called on UI thread.
- *
- * @return a [ListenableFuture] that will be complete once ui drawing is complete
- */
-// NoClassDefFoundError occurs on API 15
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-// @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-@ExperimentalTestApi
-fun View.forceRedraw(): ListenableFuture<Void> {
- val future: ResolvableFuture<Void> = ResolvableFuture.create()
-
- if (Build.VERSION.SDK_INT >= 29 && isHardwareAccelerated) {
- viewTreeObserver.registerFrameCommitCallback() { future.set(null) }
- } else {
- viewTreeObserver.addOnDrawListener(
- object : ViewTreeObserver.OnDrawListener {
- var handled = false
- override fun onDraw() {
- if (!handled) {
- handled = true
- future.set(null)
- // cannot remove on draw listener inside of onDraw
- Handler(Looper.getMainLooper()).post {
- viewTreeObserver.removeOnDrawListener(this)
- }
- }
- }
- }
- )
- }
- invalidate()
- return future
-}
-
-private fun View.generateBitmap(
- bitmapFuture: ResolvableFuture<Bitmap>,
- window: Window? = null,
-) {
- if (bitmapFuture.isCancelled) {
- return
- }
- val destBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
- when {
- Build.VERSION.SDK_INT < 26 -> generateBitmapFromDraw(destBitmap, bitmapFuture)
- this is SurfaceView -> generateBitmapFromSurfaceViewPixelCopy(destBitmap, bitmapFuture)
- else -> {
- val window = window ?: getActivity()?.window
- if (window != null) {
- generateBitmapFromPixelCopy(window, destBitmap, bitmapFuture)
- } else {
- Log.i(
- "View.captureToImage",
- "Could not find window for view. Falling back to View#draw instead of PixelCopy"
- )
- generateBitmapFromDraw(destBitmap, bitmapFuture)
- }
- }
- }
-}
-
-@SuppressWarnings("NewApi")
-private fun SurfaceView.generateBitmapFromSurfaceViewPixelCopy(
- destBitmap: Bitmap,
- bitmapFuture: ResolvableFuture<Bitmap>
-) {
- val onCopyFinished =
- PixelCopy.OnPixelCopyFinishedListener { result ->
- if (result == PixelCopy.SUCCESS) {
- bitmapFuture.set(destBitmap)
- } else {
- bitmapFuture.setException(
- RuntimeException(String.format("PixelCopy failed: %d", result))
- )
- }
- }
- PixelCopy.request(this, null, destBitmap, onCopyFinished, handler)
-}
-
-internal fun View.generateBitmapFromDraw(
- destBitmap: Bitmap,
- bitmapFuture: ResolvableFuture<Bitmap>
-) {
- destBitmap.density = resources.displayMetrics.densityDpi
- computeScroll()
- val canvas = Canvas(destBitmap)
- canvas.translate((-scrollX).toFloat(), (-scrollY).toFloat())
- draw(canvas)
- bitmapFuture.set(destBitmap)
-}
-
-private fun View.getActivity(): Activity? {
- fun Context.getActivity(): Activity? {
- return when (this) {
- is Activity -> this
- is ContextWrapper -> this.baseContext.getActivity()
- else -> null
- }
- }
- return context.getActivity()
-}
-
-private fun View.generateBitmapFromPixelCopy(
- window: Window,
- destBitmap: Bitmap,
- bitmapFuture: ResolvableFuture<Bitmap>
-) {
- val locationInWindow = intArrayOf(0, 0)
- getLocationInWindow(locationInWindow)
- val x = locationInWindow[0]
- val y = locationInWindow[1]
- val boundsInWindow = Rect(x, y, x + width, y + height)
-
- return window.generateBitmapFromPixelCopy(boundsInWindow, destBitmap, bitmapFuture)
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
deleted file mode 100644
index 070a451..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2022 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.testing.screenshot
-
-import android.app.Activity
-import android.app.Dialog
-import android.graphics.Bitmap
-import android.os.Build
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import androidx.activity.ComponentActivity
-import androidx.test.ext.junit.rules.ActivityScenarioRule
-import java.util.concurrent.TimeUnit
-import org.junit.Assert.assertEquals
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import platform.test.screenshot.DeviceEmulationRule
-import platform.test.screenshot.DeviceEmulationSpec
-import platform.test.screenshot.MaterialYouColorsRule
-import platform.test.screenshot.ScreenshotTestRule
-import platform.test.screenshot.getEmulatedDevicePathConfig
-import platform.test.screenshot.matchers.BitmapMatcher
-
-/** A rule for View screenshot diff unit tests. */
-open class ViewScreenshotTestRule(
- emulationSpec: DeviceEmulationSpec,
- private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
- assetsPathRelativeToBuildRoot: String
-) : TestRule {
- private val colorsRule = MaterialYouColorsRule()
- private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
- protected val screenshotRule =
- ScreenshotTestRule(
- SystemUIGoldenImagePathManager(
- getEmulatedDevicePathConfig(emulationSpec),
- assetsPathRelativeToBuildRoot
- )
- )
- private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
- private val roboRule =
- RuleChain.outerRule(deviceEmulationRule).around(screenshotRule).around(activityRule)
- private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule)
- private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
-
- override fun apply(base: Statement, description: Description): Statement {
- if (isRobolectric) {
- // In robolectric mode, we enable NATIVE graphics and unpack font and icu files.
- // We need to use reflection, as this library is only needed and therefore
- // only available in deviceless mode.
- val nativeLoaderClassName = "org.robolectric.nativeruntime.DefaultNativeRuntimeLoader"
- val defaultNativeRuntimeLoader = Class.forName(nativeLoaderClassName)
- System.setProperty("robolectric.graphicsMode", "NATIVE")
- defaultNativeRuntimeLoader.getMethod("injectAndLoad").invoke(null)
- }
- val ruleToApply = if (isRobolectric) roboRule else delegateRule
- return ruleToApply.apply(base, description)
- }
-
- protected fun takeScreenshot(
- mode: Mode = Mode.WrapContent,
- viewProvider: (ComponentActivity) -> View,
- beforeScreenshot: (ComponentActivity) -> Unit = {}
- ): Bitmap {
- activityRule.scenario.onActivity { activity ->
- // Make sure that the activity draws full screen and fits the whole display instead of
- // the system bars.
- val window = activity.window
- window.setDecorFitsSystemWindows(false)
-
- // Set the content.
- activity.setContentView(viewProvider(activity), mode.layoutParams)
-
- // Elevation/shadows is not deterministic when doing hardware rendering, so we disable
- // it for any view in the hierarchy.
- window.decorView.removeElevationRecursively()
-
- activity.currentFocus?.clearFocus()
- }
-
- // We call onActivity again because it will make sure that our Activity is done measuring,
- // laying out and drawing its content (that we set in the previous onActivity lambda).
- var contentView: View? = null
- activityRule.scenario.onActivity { activity ->
- // Check that the content is what we expected.
- val content = activity.requireViewById<ViewGroup>(android.R.id.content)
- assertEquals(1, content.childCount)
- contentView = content.getChildAt(0)
- beforeScreenshot(activity)
- }
-
- return if (isRobolectric) {
- contentView?.captureToBitmap()?.get(10, TimeUnit.SECONDS)
- ?: error("timeout while trying to capture view to bitmap")
- } else {
- contentView?.toBitmap() ?: error("contentView is null")
- }
- }
-
- /**
- * Compare the content of the view provided by [viewProvider] with the golden image identified
- * by [goldenIdentifier] in the context of [emulationSpec].
- */
- fun screenshotTest(
- goldenIdentifier: String,
- mode: Mode = Mode.WrapContent,
- beforeScreenshot: (ComponentActivity) -> Unit = {},
- viewProvider: (ComponentActivity) -> View
- ) {
- val bitmap = takeScreenshot(mode, viewProvider, beforeScreenshot)
- screenshotRule.assertBitmapAgainstGolden(
- bitmap,
- goldenIdentifier,
- matcher,
- )
- }
-
- /**
- * Compare the content of the dialog provided by [dialogProvider] with the golden image
- * identified by [goldenIdentifier] in the context of [emulationSpec].
- */
- fun dialogScreenshotTest(
- goldenIdentifier: String,
- dialogProvider: (Activity) -> Dialog,
- ) {
- var dialog: Dialog? = null
- activityRule.scenario.onActivity { activity ->
- dialog =
- dialogProvider(activity).apply {
- // Make sure that the dialog draws full screen and fits the whole display
- // instead of the system bars.
- window.setDecorFitsSystemWindows(false)
-
- // Disable enter/exit animations.
- create()
- window.setWindowAnimations(0)
-
- // Elevation/shadows is not deterministic when doing hardware rendering, so we
- // disable it for any view in the hierarchy.
- window.decorView.removeElevationRecursively()
-
- // Show the dialog.
- show()
- }
- }
-
- try {
- val bitmap = dialog?.toBitmap() ?: error("dialog is null")
- screenshotRule.assertBitmapAgainstGolden(
- bitmap,
- goldenIdentifier,
- matcher,
- )
- } finally {
- dialog?.dismiss()
- }
- }
-
- private fun Dialog.toBitmap(): Bitmap {
- val window = window
- return window.decorView.toBitmap(window)
- }
-
- enum class Mode(val layoutParams: LayoutParams) {
- WrapContent(LayoutParams(WRAP_CONTENT, WRAP_CONTENT)),
- MatchSize(LayoutParams(MATCH_PARENT, MATCH_PARENT)),
- MatchWidth(LayoutParams(MATCH_PARENT, WRAP_CONTENT)),
- MatchHeight(LayoutParams(WRAP_CONTENT, MATCH_PARENT)),
- }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt
deleted file mode 100644
index d34f46b..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.android.systemui.testing.screenshot
-
-import android.graphics.Bitmap
-import android.graphics.Rect
-import android.os.Handler
-import android.os.Looper
-import android.view.PixelCopy
-import android.view.Window
-import androidx.concurrent.futures.ResolvableFuture
-
-/*
- * This file was forked from androidx/test/core/view/WindowCapture.kt.
- * TODO(b/195673633): Remove this fork and use the AndroidX version instead.
- */
-fun Window.generateBitmapFromPixelCopy(
- boundsInWindow: Rect? = null,
- destBitmap: Bitmap,
- bitmapFuture: ResolvableFuture<Bitmap>
-) {
- val onCopyFinished =
- PixelCopy.OnPixelCopyFinishedListener { result ->
- if (result == PixelCopy.SUCCESS) {
- bitmapFuture.set(destBitmap)
- } else {
- bitmapFuture.setException(
- RuntimeException(String.format("PixelCopy failed: %d", result))
- )
- }
- }
- PixelCopy.request(
- this,
- boundsInWindow,
- destBitmap,
- onCopyFinished,
- Handler(Looper.getMainLooper())
- )
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
index 64234c2..e02e592 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
@@ -18,22 +18,23 @@
import android.os.Trace
import android.os.TraceNameSupplier
+import java.util.concurrent.atomic.AtomicInteger
/**
- * Run a block within a [Trace] section.
- * Calls [Trace.beginSection] before and [Trace.endSection] after the passed block.
+ * Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection]
+ * after the passed block.
*/
inline fun <T> traceSection(tag: String, block: () -> T): T =
- if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
- Trace.traceBegin(Trace.TRACE_TAG_APP, tag)
- try {
- block()
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_APP)
- }
- } else {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP, tag)
+ try {
block()
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_APP)
}
+ } else {
+ block()
+ }
class TraceUtils {
companion object {
@@ -43,6 +44,7 @@
/**
* Helper function for creating a Runnable object that implements TraceNameSupplier.
+ *
* This is useful for posting Runnables to Handlers with meaningful names.
*/
inline fun namedRunnable(tag: String, crossinline block: () -> Unit): Runnable {
@@ -51,5 +53,37 @@
override fun run() = block()
}
}
+
+ /**
+ * Cookie used for async traces. Shouldn't be public, but to use it inside inline methods
+ * there is no other way around.
+ */
+ val lastCookie = AtomicInteger(0)
+
+ /**
+ * Creates an async slice in a track called "AsyncTraces".
+ *
+ * This can be used to trace coroutine code. Note that all usages of this method will appear
+ * under a single track.
+ */
+ inline fun <T> traceAsync(method: String, block: () -> T): T =
+ traceAsync(method, "AsyncTraces", block)
+
+ /**
+ * Creates an async slice in a track with [trackName] while [block] runs.
+ *
+ * This can be used to trace coroutine code. [method] will be the name of the slice,
+ * [trackName] of the track. The track is one of the rows visible in a perfetto trace inside
+ * SystemUI process.
+ */
+ inline fun <T> traceAsync(method: String, trackName: String, block: () -> T): T {
+ val cookie = lastCookie.incrementAndGet()
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, trackName, method, cookie)
+ try {
+ return block()
+ } finally {
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, cookie)
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 91937af..4e0e8d0 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -91,9 +91,9 @@
field = value
if (value != null) {
smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
- value.smallClock.logBuffer = smallLogBuffer
+ value.smallClock.messageBuffer = smallLogBuffer
largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
- value.largeClock.logBuffer = largeLogBuffer
+ value.largeClock.messageBuffer = largeLogBuffer
value.initialize(resources, dozeAmount, 0f)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index dba1246..6c98376 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -352,6 +352,13 @@
}
/**
+ * Set if the split shade is enabled
+ */
+ public void setSplitShadeEnabled(boolean splitShadeEnabled) {
+ mSmartspaceController.setSplitShadeEnabled(splitShadeEnabled);
+ }
+
+ /**
* Set which clock should be displayed on the keyguard. The other one will be automatically
* hidden.
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 99b5d52..38c07dc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -16,8 +16,10 @@
package com.android.keyguard;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.SystemClock;
import android.text.TextUtils;
@@ -74,6 +76,7 @@
BouncerKeyguardMessageArea mSecurityMessageDisplay;
private View mEcaView;
private ConstraintLayout mContainer;
+ @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
public KeyguardPatternView(Context context) {
this(context, null);
@@ -95,14 +98,25 @@
mContext, android.R.interpolator.fast_out_linear_in));
}
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ updateMargins();
+ }
+
void onDevicePostureChanged(@DevicePostureInt int posture) {
+ mLastDevicePosture = posture;
+ updateMargins();
+ }
+
+ private void updateMargins() {
// Update the guideline based on the device posture...
float halfOpenPercentage =
mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
ConstraintSet cs = new ConstraintSet();
cs.clone(mContainer);
- cs.setGuidelinePercent(R.id.pattern_top_guideline, posture == DEVICE_POSTURE_HALF_OPENED
+ cs.setGuidelinePercent(R.id.pattern_top_guideline,
+ mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED
? halfOpenPercentage : 0.0f);
cs.applyTo(mContainer);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 6f59684..794e694 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -43,15 +43,6 @@
private long mPinLength;
private boolean mDisabledAutoConfirmation;
- /**
- * Responsible for identifying if PIN hinting is to be enabled or not
- */
- private boolean mIsPinHinting;
-
- /**
- * Responsible for identifying if auto confirm is enabled or not in Settings
- */
- private boolean mIsAutoPinConfirmEnabledInSettings;
protected KeyguardPinViewController(KeyguardPINView view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -72,9 +63,6 @@
mFeatureFlags = featureFlags;
mBackspaceKey = view.findViewById(R.id.delete_button);
mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser());
- mIsPinHinting = mPinLength == DEFAULT_PIN_LENGTH;
- mIsAutoPinConfirmEnabledInSettings = mLockPatternUtils.isAutoPinConfirmEnabled(
- KeyguardUpdateMonitor.getCurrentUser());
}
@Override
@@ -89,12 +77,13 @@
});
}
mPasswordEntry.setUserActivityListener(this::onUserInput);
+ mView.onDevicePostureChanged(mPostureController.getDevicePosture());
mPostureController.addCallback(mPostureCallback);
}
protected void onUserInput() {
super.onUserInput();
- if (mIsAutoPinConfirmEnabledInSettings) {
+ if (isAutoPinConfirmEnabledInSettings()) {
updateAutoConfirmationState();
if (mPasswordEntry.getText().length() == mPinLength
&& mOkButton.getVisibility() == View.INVISIBLE) {
@@ -142,7 +131,7 @@
* Updates the visibility of the OK button for auto confirm feature
*/
private void updateOKButtonVisibility() {
- if (mIsPinHinting && !mDisabledAutoConfirmation) {
+ if (isAutoPinConfirmEnabledInSettings() && !mDisabledAutoConfirmation) {
mOkButton.setVisibility(View.INVISIBLE);
} else {
mOkButton.setVisibility(View.VISIBLE);
@@ -154,9 +143,10 @@
* Visibility changes are only for auto confirmation configuration.
*/
private void updateBackSpaceVisibility() {
+ boolean isAutoConfirmation = isAutoPinConfirmEnabledInSettings();
mBackspaceKey.setTransparentMode(/* isTransparentMode= */
- mIsAutoPinConfirmEnabledInSettings && !mDisabledAutoConfirmation);
- if (mIsAutoPinConfirmEnabledInSettings) {
+ isAutoConfirmation && !mDisabledAutoConfirmation);
+ if (isAutoConfirmation) {
if (mPasswordEntry.getText().length() > 0
|| mDisabledAutoConfirmation) {
mBackspaceKey.setVisibility(View.VISIBLE);
@@ -166,8 +156,24 @@
}
}
/** Updates whether to use pin hinting or not. */
- private void updatePinHinting() {
- mPasswordEntry.setIsPinHinting(mIsAutoPinConfirmEnabledInSettings && mIsPinHinting
+ void updatePinHinting() {
+ mPasswordEntry.setIsPinHinting(isAutoPinConfirmEnabledInSettings() && isPinHinting()
&& !mDisabledAutoConfirmation);
}
+
+ /**
+ * Responsible for identifying if PIN hinting is to be enabled or not
+ */
+ private boolean isPinHinting() {
+ return mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser())
+ == DEFAULT_PIN_LENGTH;
+ }
+
+ /**
+ * Responsible for identifying if auto confirm is enabled or not in Settings
+ */
+ private boolean isAutoPinConfirmEnabledInSettings() {
+ //Checks if user has enabled the auto confirm in Settings
+ return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser());
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 880f242..458ca2b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -544,7 +544,6 @@
public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) {
if (mCancelAction != null) {
mCancelAction.run();
- mCancelAction = null;
}
mDismissAction = action;
mCancelAction = cancelAction;
@@ -782,9 +781,10 @@
case SimPuk:
// Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
- if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled(
- KeyguardUpdateMonitor.getCurrentUser())) {
- finish = true;
+ boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (securityMode == SecurityMode.None || isLockscreenDisabled) {
+ finish = isLockscreenDisabled;
eventSubtype = BOUNCER_DISMISS_SIM;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
} else {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 00500d6..6854c97 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -323,6 +323,13 @@
}
/**
+ * Set if the split shade is enabled
+ */
+ public void setSplitShadeEnabled(boolean enabled) {
+ mKeyguardClockSwitchController.setSplitShadeEnabled(enabled);
+ }
+
+ /**
* Updates the alignment of the KeyguardStatusView and animates the transition if requested.
*/
public void updateAlignment(
@@ -350,6 +357,9 @@
}
mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
+ /* This transition blocks any layout changes while running. For that reason
+ * special logic with setting visibility was added to {@link BcSmartspaceView#setDozing}
+ * for split shade to avoid jump of the media object. */
ChangeBounds transition = new ChangeBounds();
if (splitShadeEnabled) {
// Excluding media from the transition on split-shade, as it doesn't transition
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index d1fffaa..76b073e 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -32,7 +32,6 @@
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.settingslib.Utils;
@@ -126,7 +125,6 @@
/**
* Set the location of the lock icon.
*/
- @VisibleForTesting
public void setCenterLocation(@NonNull Point center, float radius, int drawablePadding) {
mLockIconCenter = center;
mRadius = radius;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 239a0cc..8ef6c2e 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -56,10 +56,12 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.biometrics.UdfpsController;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -112,6 +114,7 @@
@NonNull private final VibratorHelper mVibrator;
@Nullable private final AuthRippleController mAuthRippleController;
@NonNull private final FeatureFlags mFeatureFlags;
+ @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@NonNull private final KeyguardTransitionInteractor mTransitionInteractor;
@NonNull private final KeyguardInteractor mKeyguardInteractor;
@@ -180,7 +183,8 @@
@NonNull @Main Resources resources,
@NonNull KeyguardTransitionInteractor transitionInteractor,
@NonNull KeyguardInteractor keyguardInteractor,
- @NonNull FeatureFlags featureFlags
+ @NonNull FeatureFlags featureFlags,
+ PrimaryBouncerInteractor primaryBouncerInteractor
) {
super(view);
mStatusBarStateController = statusBarStateController;
@@ -197,6 +201,7 @@
mTransitionInteractor = transitionInteractor;
mKeyguardInteractor = keyguardInteractor;
mFeatureFlags = featureFlags;
+ mPrimaryBouncerInteractor = primaryBouncerInteractor;
mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
@@ -325,8 +330,14 @@
mView.setContentDescription(null);
}
+ boolean accessibilityEnabled =
+ !mPrimaryBouncerInteractor.isAnimatingAway() && mView.isVisibleToUser();
+ mView.setImportantForAccessibility(
+ accessibilityEnabled ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ : View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+
if (!Objects.equals(prevContentDescription, mView.getContentDescription())
- && mView.getContentDescription() != null && mView.isVisibleToUser()) {
+ && mView.getContentDescription() != null && accessibilityEnabled) {
mView.announceForAccessibility(mView.getContentDescription());
}
}
@@ -387,15 +398,17 @@
private void updateLockIconLocation() {
final float scaleFactor = mAuthController.getScaleFactor();
final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor);
- if (mUdfpsSupported) {
- mView.setCenterLocation(mAuthController.getUdfpsLocation(),
- mAuthController.getUdfpsRadius(), scaledPadding);
- } else {
- mView.setCenterLocation(
- new Point((int) mWidthPixels / 2,
- (int) (mHeightPixels
- - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))),
+ if (!mFeatureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+ if (mUdfpsSupported) {
+ mView.setCenterLocation(mAuthController.getUdfpsLocation(),
+ mAuthController.getUdfpsRadius(), scaledPadding);
+ } else {
+ mView.setCenterLocation(
+ new Point((int) mWidthPixels / 2,
+ (int) (mHeightPixels
+ - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))),
sLockIconRadiusPx * scaleFactor, scaledPadding);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index de7a669..ff395da 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -36,6 +36,7 @@
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.hardware.graphics.common.AlphaInterpretation;
@@ -171,7 +172,7 @@
private int mTintColor = Color.BLACK;
@VisibleForTesting
protected DisplayDecorationSupport mHwcScreenDecorationSupport;
- private Display.Mode mDisplayMode;
+ private final Point mDisplaySize = new Point();
@VisibleForTesting
protected DisplayInfo mDisplayInfo = new DisplayInfo();
private DisplayCutout mDisplayCutout;
@@ -484,7 +485,8 @@
mWindowManager = mContext.getSystemService(WindowManager.class);
mContext.getDisplay().getDisplayInfo(mDisplayInfo);
mRotation = mDisplayInfo.rotation;
- mDisplayMode = mDisplayInfo.getMode();
+ mDisplaySize.x = mDisplayInfo.getNaturalWidth();
+ mDisplaySize.y = mDisplayInfo.getNaturalHeight();
mDisplayUniqueId = mDisplayInfo.uniqueId;
mDisplayCutout = mDisplayInfo.displayCutout;
mRoundedCornerResDelegate =
@@ -505,10 +507,12 @@
public void onDisplayChanged(int displayId) {
mContext.getDisplay().getDisplayInfo(mDisplayInfo);
final int newRotation = mDisplayInfo.rotation;
- final Display.Mode newDisplayMode = mDisplayInfo.getMode();
if ((mOverlays != null || mScreenDecorHwcWindow != null)
&& (mRotation != newRotation
- || displayModeChanged(mDisplayMode, newDisplayMode))) {
+ || displaySizeChanged(mDisplaySize, mDisplayInfo))) {
+ final Point newSize = new Point();
+ newSize.x = mDisplayInfo.getNaturalWidth();
+ newSize.y = mDisplayInfo.getNaturalHeight();
// We cannot immediately update the orientation. Otherwise
// WindowManager is still deferring layout until it has finished dispatching
// the config changes, which may cause divergence between what we draw
@@ -520,9 +524,8 @@
if (mRotation != newRotation) {
mLogger.logRotationChangeDeferred(mRotation, newRotation);
}
- if (displayModeChanged(mDisplayMode, newDisplayMode)) {
- mLogger.logDisplayModeChanged(
- newDisplayMode.getModeId(), mDisplayMode.getModeId());
+ if (!mDisplaySize.equals(newSize)) {
+ mLogger.logDisplaySizeChanged(mDisplaySize, newSize);
}
if (mOverlays != null) {
@@ -531,7 +534,7 @@
final ViewGroup overlayView = mOverlays[i].getRootView();
overlayView.getViewTreeObserver().addOnPreDrawListener(
new RestartingPreDrawListener(
- overlayView, i, newRotation, newDisplayMode));
+ overlayView, i, newRotation, newSize));
}
}
}
@@ -541,7 +544,7 @@
new RestartingPreDrawListener(
mScreenDecorHwcWindow,
-1, // Pass -1 for views with no specific position.
- newRotation, newDisplayMode));
+ newRotation, newSize));
}
if (mScreenDecorHwcLayer != null) {
mScreenDecorHwcLayer.pendingConfigChange = true;
@@ -943,15 +946,8 @@
}
}
- private static boolean displayModeChanged(Display.Mode oldMode, Display.Mode newMode) {
- if (oldMode == null) {
- return true;
- }
-
- // We purposely ignore refresh rate and id changes here, because we don't need to
- // invalidate for those, and they can trigger the refresh rate to increase
- return oldMode.getPhysicalWidth() != newMode.getPhysicalWidth()
- || oldMode.getPhysicalHeight() != newMode.getPhysicalHeight();
+ private static boolean displaySizeChanged(Point size, DisplayInfo info) {
+ return size.x != info.getNaturalWidth() || size.y != info.getNaturalHeight();
}
private int getOverlayWindowGravity(@BoundsPosition int pos) {
@@ -1170,14 +1166,14 @@
if (mRotation != newRotation) {
mDotViewController.setNewRotation(newRotation);
}
- final Display.Mode newMod = mDisplayInfo.getMode();
final DisplayCutout newCutout = mDisplayInfo.displayCutout;
if (!mPendingConfigChange
- && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod)
+ && (newRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo)
|| !Objects.equals(newCutout, mDisplayCutout))) {
mRotation = newRotation;
- mDisplayMode = newMod;
+ mDisplaySize.x = mDisplayInfo.getNaturalWidth();
+ mDisplaySize.y = mDisplayInfo.getNaturalHeight();
mDisplayCutout = newCutout;
float ratio = getPhysicalPixelDisplaySizeRatio();
mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(ratio);
@@ -1494,31 +1490,29 @@
private final View mView;
private final int mTargetRotation;
- private final Display.Mode mTargetDisplayMode;
+ private final Point mTargetDisplaySize;
// Pass -1 for ScreenDecorHwcLayer since it's a fullscreen window and has no specific
// position.
private final int mPosition;
private RestartingPreDrawListener(View view, @BoundsPosition int position,
- int targetRotation, Display.Mode targetDisplayMode) {
+ int targetRotation, Point targetDisplaySize) {
mView = view;
mTargetRotation = targetRotation;
- mTargetDisplayMode = targetDisplayMode;
+ mTargetDisplaySize = targetDisplaySize;
mPosition = position;
}
@Override
public boolean onPreDraw() {
mView.getViewTreeObserver().removeOnPreDrawListener(this);
- if (mTargetRotation == mRotation
- && !displayModeChanged(mDisplayMode, mTargetDisplayMode)) {
+ if (mTargetRotation == mRotation && mDisplaySize.equals(mTargetDisplaySize)) {
if (DEBUG_LOGGING) {
final String title = mPosition < 0 ? "ScreenDecorHwcLayer"
: getWindowTitleByPos(mPosition);
Log.i(TAG, title + " already in target rot "
+ mTargetRotation + " and in target resolution "
- + mTargetDisplayMode.getPhysicalWidth() + "x"
- + mTargetDisplayMode.getPhysicalHeight()
+ + mTargetDisplaySize.x + "x" + mTargetDisplaySize.y
+ ", allow draw without restarting it");
}
return true;
@@ -1533,8 +1527,7 @@
: getWindowTitleByPos(mPosition);
Log.i(TAG, title
+ " restarting listener fired, restarting draw for rot " + mRotation
- + ", resolution " + mDisplayMode.getPhysicalWidth() + "x"
- + mDisplayMode.getPhysicalHeight());
+ + ", resolution " + mDisplaySize.x + "x" + mDisplaySize.y);
}
mView.invalidate();
return false;
@@ -1560,19 +1553,18 @@
public boolean onPreDraw() {
mContext.getDisplay().getDisplayInfo(mDisplayInfo);
final int displayRotation = mDisplayInfo.rotation;
- final Display.Mode displayMode = mDisplayInfo.getMode();
- if ((displayRotation != mRotation || displayModeChanged(mDisplayMode, displayMode))
+ if ((displayRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo))
&& !mPendingConfigChange) {
if (DEBUG_LOGGING) {
if (displayRotation != mRotation) {
Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot "
+ displayRotation + ". Restarting draw");
}
- if (displayModeChanged(mDisplayMode, displayMode)) {
- Log.i(TAG, "Drawing at " + mDisplayMode.getPhysicalWidth()
- + "x" + mDisplayMode.getPhysicalHeight() + ", but display is at "
- + displayMode.getPhysicalWidth() + "x"
- + displayMode.getPhysicalHeight() + ". Restarting draw");
+ if (displaySizeChanged(mDisplaySize, mDisplayInfo)) {
+ Log.i(TAG, "Drawing at " + mDisplaySize.x + "x" + mDisplaySize.y
+ + ", but display is at "
+ + mDisplayInfo.getNaturalWidth() + "x"
+ + mDisplayInfo.getNaturalHeight() + ". Restarting draw");
}
}
mView.invalidate();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 2a14dc8..1739ba4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -28,6 +28,7 @@
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.Handler;
+import android.util.SparseArray;
import android.view.Display;
import android.view.SurfaceControl;
import android.view.WindowManagerGlobal;
@@ -72,6 +73,9 @@
private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl;
private SysUiState mSysUiState;
+ @VisibleForTesting
+ SparseArray<SparseArray<Float>> mUsersScales = new SparseArray();
+
private static class ControllerSupplier extends
DisplayIdIndexSupplier<WindowMagnificationController> {
@@ -295,6 +299,19 @@
mModeSwitchesController.removeButton(displayId);
}
+ @MainThread
+ void setUserMagnificationScale(int userId, int displayId, float scale) {
+ SparseArray<Float> scales = mUsersScales.get(userId);
+ if (scales == null) {
+ scales = new SparseArray<>();
+ mUsersScales.put(userId, scales);
+ }
+ if (scales.contains(displayId) && scales.get(displayId) == scale) {
+ return;
+ }
+ scales.put(displayId, scale);
+ }
+
@VisibleForTesting
final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
index c081893..f1d00ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
@@ -99,6 +99,12 @@
}
@Override
+ public void onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
+ mHandler.post(() -> mWindowMagnification.setUserMagnificationScale(
+ userId, displayId, scale));
+ }
+
+ @Override
public void setConnectionCallback(IWindowMagnificationConnectionCallback callback) {
mConnectionCallback = callback;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 31b0f056..1e1d4b7 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -172,7 +172,8 @@
};
}
- private class ZoomSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener {
+ private class ZoomSeekbarChangeListener implements
+ SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
float scale = (progress / (float) mSeekBarMagnitude) + SCALE_MIN_VALUE;
@@ -197,6 +198,11 @@
public void onStopTrackingTouch(SeekBar seekBar) {
// Do nothing
}
+
+ @Override
+ public void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control) {
+ // Do nothing
+ }
}
private final AccessibilityDelegate mPanelDelegate = new AccessibilityDelegate() {
@@ -521,7 +527,7 @@
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0,
UserHandle.USER_CURRENT);
setScaleSeekbar(scale);
- mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener());
+ mZoomSeekbar.setOnSeekBarWithIconButtonsChangeListener(new ZoomSeekbarChangeListener());
mAllowDiagonalScrollingView =
(LinearLayout) mSettingView.findViewById(R.id.magnifier_horizontal_lock_view);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
index a910ab5..783460c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
@@ -26,12 +26,13 @@
import android.view.LayoutInflater
import android.widget.Button
import android.widget.SeekBar
-import android.widget.SeekBar.OnSeekBarChangeListener
import android.widget.TextView
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import com.android.systemui.R
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener.ControlUnitType
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.UserTracker
@@ -105,28 +106,32 @@
lastProgress.set(fontSizeValueToIndex(currentScale))
seekBarWithIconButtonsView.setProgress(lastProgress.get())
- seekBarWithIconButtonsView.setOnSeekBarChangeListener(
- object : OnSeekBarChangeListener {
- var isTrackingTouch = false
-
+ seekBarWithIconButtonsView.setOnSeekBarWithIconButtonsChangeListener(
+ object : OnSeekBarWithIconButtonsChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
// Always provide preview configuration for text first when there is a change
// in the seekbar progress.
createTextPreview(progress)
-
- if (!isTrackingTouch) {
- // The seekbar progress is changed by icon buttons
- changeFontSize(progress, CHANGE_BY_BUTTON_DELAY_MS)
- }
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
- isTrackingTouch = true
+ // Do nothing
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
- isTrackingTouch = false
- changeFontSize(seekBar.progress, CHANGE_BY_SEEKBAR_DELAY_MS)
+ // Do nothing
+ }
+
+ override fun onUserInteractionFinalized(
+ seekBar: SeekBar,
+ @ControlUnitType control: Int
+ ) {
+ if (control == ControlUnitType.BUTTON) {
+ // The seekbar progress is changed by icon buttons
+ changeFontSize(seekBar.progress, CHANGE_BY_BUTTON_DELAY_MS)
+ } else {
+ changeFontSize(seekBar.progress, CHANGE_BY_SEEKBAR_DELAY_MS)
+ }
}
}
)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index e6aeb43..802eea3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -37,4 +37,8 @@
dumpManager
) {
override val tag = "UdfpsBpViewController"
+
+ override fun shouldPauseAuth(): Boolean {
+ return false
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index dc9ba87..ebff0b0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -85,6 +85,8 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter;
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -153,6 +155,7 @@
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+ @NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
@NonNull private final VibratorHelper mVibrator;
@NonNull private final FeatureFlags mFeatureFlags;
@NonNull private final FalsingManager mFalsingManager;
@@ -272,7 +275,8 @@
(view, event, fromUdfpsView) -> onTouch(requestId, event,
fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils,
- mUdfpsKeyguardAccessibilityDelegate)));
+ mUdfpsKeyguardAccessibilityDelegate,
+ mUdfpsKeyguardViewModels)));
}
@Override
@@ -591,6 +595,13 @@
// Pilfer if valid overlap, don't allow following events to reach keyguard
shouldPilfer = true;
+
+ // Touch is a valid UDFPS touch. Inform the falsing manager so that the touch
+ // isn't counted against the falsing algorithm as an accidental touch.
+ // We do this on the DOWN event instead of CANCEL/UP because the CANCEL/UP events
+ // get sent too late to this receiver (after the actual cancel/up motions occur),
+ // and therefore wouldn't end up being used as part of the falsing algo.
+ mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION);
break;
case UP:
@@ -610,7 +621,6 @@
data.getTime(),
data.getGestureStart(),
mStatusBarStateController.isDozing());
- mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION);
break;
case UNCHANGED:
@@ -784,7 +794,7 @@
private boolean shouldTryToDismissKeyguard() {
return mOverlay != null
&& mOverlay.getAnimationViewController()
- instanceof UdfpsKeyguardViewControllerLegacy
+ instanceof UdfpsKeyguardViewControllerAdapter
&& mKeyguardStateController.canDismissLockScreen()
&& !mAttemptedToDismissKeyguard;
}
@@ -829,7 +839,8 @@
@NonNull InputManager inputManager,
@NonNull UdfpsUtils udfpsUtils,
@NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
- @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate) {
+ @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
+ @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -895,6 +906,7 @@
return Unit.INSTANCE;
});
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+ mUdfpsKeyguardViewModels = udfpsKeyguardViewModelsProvider;
final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index e542147..d6ef94d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -46,15 +46,19 @@
import androidx.annotation.LayoutRes
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.settingslib.udfps.UdfpsUtils
import com.android.settingslib.udfps.UdfpsOverlayParams
+import com.android.settingslib.udfps.UdfpsUtils
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -64,6 +68,8 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.settings.SecureSettings
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import javax.inject.Provider
private const val TAG = "UdfpsControllerOverlay"
@@ -75,6 +81,7 @@
* request. This state can persist across configuration changes via the [show] and [hide]
* methods.
*/
+@ExperimentalCoroutinesApi
@UiThread
class UdfpsControllerOverlay @JvmOverloads constructor(
private val context: Context,
@@ -105,6 +112,7 @@
private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
private val udfpsUtils: UdfpsUtils,
private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
+ private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>,
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
@@ -243,27 +251,40 @@
)
}
REASON_AUTH_KEYGUARD -> {
- UdfpsKeyguardViewControllerLegacy(
- view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) {
- updateSensorLocation(sensorBounds)
- },
- statusBarStateController,
- shadeExpansionStateManager,
- statusBarKeyguardViewManager,
- keyguardUpdateMonitor,
- dumpManager,
- transitionController,
- configurationController,
- keyguardStateController,
- unlockedScreenOffAnimationController,
- dialogManager,
- controller,
- activityLaunchAnimator,
- featureFlags,
- primaryBouncerInteractor,
- alternateBouncerInteractor,
- udfpsKeyguardAccessibilityDelegate,
- )
+ if (featureFlags.isEnabled(REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+ udfpsKeyguardViewModels.get().setSensorBounds(sensorBounds)
+ UdfpsKeyguardViewController(
+ view.addUdfpsView(R.layout.udfps_keyguard_view),
+ statusBarStateController,
+ shadeExpansionStateManager,
+ dialogManager,
+ dumpManager,
+ alternateBouncerInteractor,
+ udfpsKeyguardViewModels.get(),
+ )
+ } else {
+ UdfpsKeyguardViewControllerLegacy(
+ view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) {
+ updateSensorLocation(sensorBounds)
+ },
+ statusBarStateController,
+ shadeExpansionStateManager,
+ statusBarKeyguardViewManager,
+ keyguardUpdateMonitor,
+ dumpManager,
+ transitionController,
+ configurationController,
+ keyguardStateController,
+ unlockedScreenOffAnimationController,
+ dialogManager,
+ controller,
+ activityLaunchAnimator,
+ featureFlags,
+ primaryBouncerInteractor,
+ alternateBouncerInteractor,
+ udfpsKeyguardAccessibilityDelegate,
+ )
+ }
}
REASON_AUTH_BP -> {
// note: empty controller, currently shows no visual affordance
@@ -415,7 +436,7 @@
}
private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean {
- if (animation !is UdfpsKeyguardViewControllerLegacy) {
+ if (animation !is UdfpsKeyguardViewControllerAdapter) {
// always rotate view if we're not on the keyguard
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
new file mode 100644
index 0000000..8cc15da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 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.biometrics
+
+import android.content.Context
+import android.util.AttributeSet
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** View corresponding with udfps_keyguard_view.xml */
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardView(
+ context: Context,
+ attrs: AttributeSet?,
+) :
+ UdfpsAnimationView(
+ context,
+ attrs,
+ ) {
+ private val fingerprintDrawablePlaceHolder = UdfpsFpDrawable(context)
+ private var visible = false
+
+ override fun calculateAlpha(): Int {
+ return if (mPauseAuth) {
+ 0
+ } else 255 // ViewModels handle animating alpha values
+ }
+
+ override fun getDrawable(): UdfpsDrawable {
+ return fingerprintDrawablePlaceHolder
+ }
+
+ fun useExpandedOverlay(useExpandedOverlay: Boolean) {
+ mUseExpandedOverlay = useExpandedOverlay
+ }
+
+ fun isVisible(): Boolean {
+ return visible
+ }
+
+ fun setVisible(isVisible: Boolean) {
+ visible = isVisible
+ isPauseAuth = !visible
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 9bafeec..15bd731 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -34,6 +34,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionListener
@@ -80,7 +81,8 @@
shadeExpansionStateManager,
systemUIDialogManager,
dumpManager,
- ) {
+ ),
+ UdfpsKeyguardViewControllerAdapter {
private val useExpandedOverlay: Boolean =
featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
private var showingUdfpsBouncer = false
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
new file mode 100644
index 0000000..2a9f3ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 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.biometrics.ui.controller
+
+import com.android.systemui.biometrics.UdfpsAnimationViewController
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Class that coordinates non-HBM animations during keyguard authentication. */
+@ExperimentalCoroutinesApi
+open class UdfpsKeyguardViewController(
+ val view: UdfpsKeyguardView,
+ statusBarStateController: StatusBarStateController,
+ shadeExpansionStateManager: ShadeExpansionStateManager,
+ systemUIDialogManager: SystemUIDialogManager,
+ dumpManager: DumpManager,
+ private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ udfpsKeyguardViewModels: UdfpsKeyguardViewModels,
+) :
+ UdfpsAnimationViewController<UdfpsKeyguardView>(
+ view,
+ statusBarStateController,
+ shadeExpansionStateManager,
+ systemUIDialogManager,
+ dumpManager,
+ ),
+ UdfpsKeyguardViewControllerAdapter {
+ override val tag: String
+ get() = TAG
+
+ init {
+ udfpsKeyguardViewModels.bindViews(view)
+ }
+
+ public override fun onViewAttached() {
+ super.onViewAttached()
+ alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
+ }
+
+ public override fun onViewDetached() {
+ super.onViewDetached()
+ alternateBouncerInteractor.setAlternateBouncerUIAvailable(false)
+ }
+
+ override fun shouldPauseAuth(): Boolean {
+ return !view.isVisible()
+ }
+
+ override fun listenForTouchesOutsideView(): Boolean {
+ return true
+ }
+
+ companion object {
+ private const val TAG = "UdfpsKeyguardViewController"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index b293ea6..db6ca0b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -61,6 +61,7 @@
private val pin: PinBouncerViewModel by lazy {
PinBouncerViewModel(
+ applicationContext = applicationContext,
applicationScope = applicationScope,
interactor = interactor,
isInputEnabled = isInputEnabled,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 014ebc3..641e863 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -16,6 +16,8 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.content.Context
+import com.android.keyguard.PinShapeAdapter
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import kotlinx.coroutines.CoroutineScope
@@ -29,6 +31,7 @@
/** Holds UI state and handles user input for the PIN code bouncer UI. */
class PinBouncerViewModel(
+ applicationContext: Context,
private val applicationScope: CoroutineScope,
private val interactor: BouncerInteractor,
isInputEnabled: StateFlow<Boolean>,
@@ -37,6 +40,8 @@
isInputEnabled = isInputEnabled,
) {
+ val pinShapes = PinShapeAdapter(applicationContext)
+
private val mutablePinEntries = MutableStateFlow<List<EnteredKey>>(emptyList())
val pinEntries: StateFlow<List<EnteredKey>> = mutablePinEntries
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
index 4538a6c..277b427 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -16,6 +16,7 @@
package com.android.systemui.common.ui.view;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.TypedArray;
@@ -30,6 +31,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* The layout contains a seekbar whose progress could be modified
* through the icons on two ends of the seekbar.
@@ -47,6 +51,8 @@
private SeekBar mSeekbar;
private int mSeekBarChangeMagnitude = 1;
+ private boolean mSetProgressFromButtonFlag = false;
+
private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener();
private String[] mStateLabels = null;
@@ -121,21 +127,8 @@
mSeekbar.setOnSeekBarChangeListener(mSeekBarListener);
- mIconStartFrame.setOnClickListener((view) -> {
- final int progress = mSeekbar.getProgress();
- if (progress > 0) {
- mSeekbar.setProgress(progress - mSeekBarChangeMagnitude);
- setIconViewAndFrameEnabled(mIconStart, mSeekbar.getProgress() > 0);
- }
- });
-
- mIconEndFrame.setOnClickListener((view) -> {
- final int progress = mSeekbar.getProgress();
- if (progress < mSeekbar.getMax()) {
- mSeekbar.setProgress(progress + mSeekBarChangeMagnitude);
- setIconViewAndFrameEnabled(mIconEnd, mSeekbar.getProgress() < mSeekbar.getMax());
- }
- });
+ mIconStartFrame.setOnClickListener((view) -> onIconStartClicked());
+ mIconEndFrame.setOnClickListener((view) -> onIconEndClicked());
}
private static void setIconViewAndFrameEnabled(View iconView, boolean enabled) {
@@ -172,9 +165,9 @@
* Sets a onSeekbarChangeListener to the seekbar in the layout.
* We update the Start Icon and End Icon if needed when the seekbar progress is changed.
*/
- public void setOnSeekBarChangeListener(
- @Nullable SeekBar.OnSeekBarChangeListener onSeekBarChangeListener) {
- mSeekBarListener.setOnSeekBarChangeListener(onSeekBarChangeListener);
+ public void setOnSeekBarWithIconButtonsChangeListener(
+ @Nullable OnSeekBarWithIconButtonsChangeListener onSeekBarChangeListener) {
+ mSeekBarListener.setOnSeekBarWithIconButtonsChangeListener(onSeekBarChangeListener);
}
/**
@@ -216,16 +209,72 @@
*/
public void setProgress(int progress) {
mSeekbar.setProgress(progress);
- updateIconViewIfNeeded(progress);
+ updateIconViewIfNeeded(mSeekbar.getProgress());
}
+ private void setProgressFromButton(int progress) {
+ mSetProgressFromButtonFlag = true;
+ mSeekbar.setProgress(progress);
+ updateIconViewIfNeeded(mSeekbar.getProgress());
+ }
+
+ private void onIconStartClicked() {
+ final int progress = mSeekbar.getProgress();
+ if (progress > 0) {
+ setProgressFromButton(progress - mSeekBarChangeMagnitude);
+ }
+ }
+
+ private void onIconEndClicked() {
+ final int progress = mSeekbar.getProgress();
+ if (progress < mSeekbar.getMax()) {
+ setProgressFromButton(progress + mSeekBarChangeMagnitude);
+ }
+ }
+
+ /**
+ * Get current seekbar progress
+ *
+ * @return
+ */
@VisibleForTesting
public int getProgress() {
return mSeekbar.getProgress();
}
+ /**
+ * Extended from {@link SeekBar.OnSeekBarChangeListener} to add callback to notify the listeners
+ * the user interaction with the SeekBarWithIconButtonsView is finalized.
+ */
+ public interface OnSeekBarWithIconButtonsChangeListener
+ extends SeekBar.OnSeekBarChangeListener {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ ControlUnitType.SLIDER,
+ ControlUnitType.BUTTON
+ })
+ /** Denotes the Last user interacted control unit type. */
+ @interface ControlUnitType {
+ int SLIDER = 0;
+ int BUTTON = 1;
+ }
+
+ /**
+ * Notification that the user interaction with SeekBarWithIconButtonsView is finalized. This
+ * would be triggered after user ends dragging on the slider or clicks icon buttons.
+ *
+ * @param seekBar The SeekBar in which the user ends interaction with
+ * @param control The last user interacted control unit. It would be
+ * {@link ControlUnitType#SLIDER} if the user was changing the seekbar
+ * progress through dragging the slider, or {@link ControlUnitType#BUTTON}
+ * is the user was clicking button to change the progress.
+ */
+ void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control);
+ }
+
private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
- private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = null;
+ private OnSeekBarWithIconButtonsChangeListener mOnSeekBarChangeListener = null;
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
@@ -233,7 +282,17 @@
setSeekbarStateDescription();
}
if (mOnSeekBarChangeListener != null) {
- mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
+ if (mSetProgressFromButtonFlag) {
+ mSetProgressFromButtonFlag = false;
+ mOnSeekBarChangeListener.onProgressChanged(
+ seekBar, progress, /* fromUser= */ true);
+ // Directly trigger onUserInteractionFinalized since the interaction
+ // (click button) is ended.
+ mOnSeekBarChangeListener.onUserInteractionFinalized(
+ seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON);
+ } else {
+ mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
+ }
}
updateIconViewIfNeeded(progress);
}
@@ -249,10 +308,13 @@
public void onStopTrackingTouch(SeekBar seekBar) {
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onStopTrackingTouch(seekBar);
+ mOnSeekBarChangeListener.onUserInteractionFinalized(
+ seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER);
}
}
- void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener listener) {
+ void setOnSeekBarWithIconButtonsChangeListener(
+ OnSeekBarWithIconButtonsChangeListener listener) {
mOnSeekBarChangeListener = listener;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
index ce0f2e9..501bcf0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
@@ -135,7 +135,7 @@
val listener = DialogListener(prefs, attempts, onAttemptCompleted)
val d =
dialogProvider(activityContext, R.style.Theme_SystemUI_Dialog).apply {
- setIcon(R.drawable.ic_warning)
+ setIcon(R.drawable.ic_lock_locked)
setOnCancelListener(listener)
setNeutralButton(R.string.controls_settings_dialog_neutral_button, listener)
setPositiveButton(R.string.controls_settings_dialog_positive_button, listener)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 76002d3..b1f513d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -42,6 +42,7 @@
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
+import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
import com.android.systemui.power.PowerUI
import com.android.systemui.reardisplay.RearDisplayDialogController
import com.android.systemui.recents.Recents
@@ -49,6 +50,7 @@
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.notification.InstantAppNotifier
import com.android.systemui.statusbar.phone.KeyguardLiftController
+import com.android.systemui.statusbar.phone.LockscreenWallpaper
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
@@ -110,6 +112,14 @@
@ClassKey(KeyboardUI::class)
abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable
+ /** Inject into MediaProjectionTaskSwitcherCoreStartable. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaProjectionTaskSwitcherCoreStartable::class)
+ abstract fun bindProjectedTaskListener(
+ sysui: MediaProjectionTaskSwitcherCoreStartable
+ ): CoreStartable
+
/** Inject into KeyguardBiometricLockoutLogger */
@Binds
@IntoMap
@@ -301,4 +311,9 @@
@IntoMap
@ClassKey(KeyguardViewConfigurator::class)
abstract fun bindKeyguardViewConfigurator(impl: KeyguardViewConfigurator): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(LockscreenWallpaper::class)
+ abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 8f3c3d6..3b89739 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -54,10 +54,12 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagsModule;
import com.android.systemui.keyboard.KeyboardModule;
+import com.android.systemui.keyguard.ui.view.layout.LockscreenLayoutModule;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.log.dagger.MonitorLog;
import com.android.systemui.log.table.TableLogBuffer;
import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
+import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.motiontool.MotionToolModule;
import com.android.systemui.navigationbar.NavigationBarComponent;
@@ -178,8 +180,10 @@
GarbageMonitorModule.class,
KeyboardModule.class,
LetterboxModule.class,
+ LockscreenLayoutModule.class,
LogModule.class,
MediaProjectionModule.class,
+ MediaProjectionTaskSwitcherModule.class,
MotionToolModule.class,
PeopleHubModule.class,
PeopleModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 94c8551..f892a97 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -89,7 +89,16 @@
// TODO(b/277338665): Tracking Bug
@JvmField
val NOTIFICATION_SHELF_REFACTOR =
- unreleasedFlag(271161129, "notification_shelf_refactor")
+ unreleasedFlag(271161129, "notification_shelf_refactor", teamfood = true)
+
+ // TODO(b/288326013): Tracking Bug
+ @JvmField
+ val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION =
+ unreleasedFlag(
+ 288326013,
+ "notification_async_hybrid_view_inflation",
+ teamfood = false
+ )
@JvmField
val ANIMATED_NOTIFICATION_SHADE_INSETS =
@@ -137,6 +146,14 @@
val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = releasedFlag(208,
"lockscreen_without_secure_lock_when_dreaming")
+ // TODO(b/286092087): Tracking Bug
+ @JvmField
+ val ENABLE_SYSTEM_UI_DREAM_CONTROLLER = unreleasedFlag(301, "enable_system_ui_dream_controller")
+
+ // TODO(b/288287730): Tracking Bug
+ @JvmField
+ val ENABLE_SYSTEM_UI_DREAM_HOSTING = unreleasedFlag(302, "enable_system_ui_dream_hosting")
+
/**
* Whether the clock on a wide lock screen should use the new "stepping" animation for moving
* the digits when the clock moves.
@@ -251,11 +268,6 @@
@JvmField
val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area", teamfood = true)
- /** Migrate the lock icon view to the new keyguard root view. */
- // TODO(b/286552209): Tracking bug.
- @JvmField
- val MIGRATE_LOCK_ICON = unreleasedFlag(238, "migrate_lock_icon")
-
/** Whether to listen for fingerprint authentication over keyguard occluding activities. */
// TODO(b/283260512): Tracking bug.
@JvmField
@@ -270,6 +282,11 @@
@JvmField
val TRANSIT_CLOCK = unreleasedFlag(239, "lockscreen_custom_transit_clock")
+ /** Migrate the lock icon view to the new keyguard root view. */
+ // TODO(b/286552209): Tracking bug.
+ @JvmField
+ val MIGRATE_LOCK_ICON = unreleasedFlag(240, "migrate_lock_icon")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -420,9 +437,6 @@
// TODO(b/263272731): Tracking Bug
val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag(910, "media_ttt_receiver_success_ripple")
- // TODO(b/263512203): Tracking Bug
- val MEDIA_EXPLICIT_INDICATOR = releasedFlag(911, "media_explicit_indicator")
-
// TODO(b/265813373): Tracking Bug
val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE = releasedFlag(912, "media_ttt_dismiss_gesture")
@@ -550,6 +564,12 @@
val WALLPAPER_MULTI_CROP =
sysPropBooleanFlag(1118, "persist.wm.debug.wallpaper_multi_crop", default = false)
+ // TODO(b/290220798): Tracking Bug
+ @Keep
+ @JvmField
+ val ENABLE_PIP2_IMPLEMENTATION =
+ sysPropBooleanFlag(1119, "persist.wm.debug.enable_pip2_implementation", default = false)
+
// 1200 - predictive back
@Keep
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 0511314..732102e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -50,6 +50,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.R;
import com.android.systemui.SystemUIAppComponentFactoryBase;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -151,6 +152,9 @@
private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
@Inject
WakeLockLogger mWakeLockLogger;
+ @Inject
+ @Background
+ Handler mBgHandler;
/**
* Receiver responsible for time ticking and updating the date format.
@@ -502,7 +506,7 @@
}
protected void notifyChange() {
- mContentResolver.notifyChange(mSliceUri, null /* observer */);
+ mBgHandler.post(() -> mContentResolver.notifyChange(mSliceUri, null /* observer */));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index e8881a4..f59ad90 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -26,6 +26,8 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager
+import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommandListener
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.statusbar.KeyguardIndicationController
@@ -42,6 +44,8 @@
private val notificationShadeWindowView: NotificationShadeWindowView,
private val featureFlags: FeatureFlags,
private val indicationController: KeyguardIndicationController,
+ private val keyguardLayoutManager: KeyguardLayoutManager,
+ private val keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener,
) : CoreStartable {
private var indicationAreaHandle: DisposableHandle? = null
@@ -51,6 +55,8 @@
notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
bindIndicationArea(notificationPanel)
bindLockIconView(notificationPanel)
+ keyguardLayoutManager.layoutViews()
+ keyguardLayoutManagerCommandListener.start()
}
fun bindIndicationArea(legacyParent: ViewGroup) {
@@ -59,7 +65,7 @@
// At startup, 2 views with the ID `R.id.keyguard_indication_area` will be available.
// Disable one of them
if (featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) {
- legacyParent.requireViewById<View>(R.id.keyguard_indication_area).let {
+ legacyParent.findViewById<View>(R.id.keyguard_indication_area)?.let {
legacyParent.removeView(it)
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index fdec209..468d760 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -140,6 +140,7 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -545,6 +546,8 @@
private CentralSurfaces mCentralSurfaces;
+ private IRemoteAnimationFinishedCallback mUnoccludeFromDreamFinishedCallback;
+
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
new DeviceConfig.OnPropertiesChangedListener() {
@Override
@@ -582,17 +585,9 @@
@Override
public void onUserSwitching(int userId) {
if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
- // Note that the mLockPatternUtils user has already been updated from setCurrentUser.
- // We need to force a reset of the views, since lockNow (called by
- // ActivityManagerService) will not reconstruct the keyguard if it is already showing.
synchronized (KeyguardViewMediator.this) {
resetKeyguardDonePendingLocked();
- if (mLockPatternUtils.isLockScreenDisabled(userId)) {
- // If we are switching to a user that has keyguard disabled, dismiss keyguard.
- dismiss(null /* callback */, null /* message */);
- } else {
- resetStateLocked();
- }
+ dismiss(null /* callback */, null /* message */);
adjustStatusBarLocked();
}
}
@@ -600,16 +595,9 @@
@Override
public void onUserSwitchComplete(int userId) {
if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
- if (userId != UserHandle.USER_SYSTEM) {
- UserInfo info = UserManager.get(mContext).getUserInfo(userId);
- // Don't try to dismiss if the user has Pin/Pattern/Password set
- if (info == null || mLockPatternUtils.isSecure(userId)) {
- return;
- } else if (info.isGuest() || info.isDemo()) {
- // If we just switched to a guest, try to dismiss keyguard.
- dismiss(null /* callback */, null /* message */);
- }
- }
+ // We are calling dismiss again and with a delay as there are race conditions
+ // in some scenarios caused by async layout listeners
+ mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500);
}
@Override
@@ -1176,6 +1164,7 @@
getRemoteSurfaceAlphaApplier().accept(0.0f);
mDreamingToLockscreenTransitionViewModel.get()
.startTransition();
+ mUnoccludeFromDreamFinishedCallback = finishedCallback;
return;
}
@@ -1255,6 +1244,19 @@
};
}
+ private Consumer<TransitionStep> getFinishedCallbackConsumer() {
+ return (TransitionStep step) -> {
+ if (mUnoccludeFromDreamFinishedCallback == null) return;
+ try {
+ mUnoccludeFromDreamFinishedCallback.onAnimationFinished();
+ mUnoccludeFromDreamFinishedCallback = null;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Wasn't able to callback", e);
+ }
+ mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
+ };
+ }
+
private DeviceConfigProxy mDeviceConfig;
private DozeParameters mDozeParameters;
@@ -1520,6 +1522,9 @@
collectFlow(viewRootImpl.getView(),
mDreamingToLockscreenTransitionViewModel.get().getDreamOverlayAlpha(),
getRemoteSurfaceAlphaApplier(), mMainDispatcher);
+ collectFlow(viewRootImpl.getView(),
+ mDreamingToLockscreenTransitionViewModel.get().getTransitionEnded(),
+ getFinishedCallbackConsumer(), mMainDispatcher);
}
}
// Most services aren't available until the system reaches the ready state, so we
@@ -2382,58 +2387,72 @@
private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
@Override
public void handleMessage(Message msg) {
+ String message = "";
switch (msg.what) {
case SHOW:
+ message = "SHOW";
handleShow((Bundle) msg.obj);
break;
case HIDE:
+ message = "HIDE";
handleHide();
break;
case RESET:
+ message = "RESET";
handleReset(msg.arg1 != 0);
break;
case VERIFY_UNLOCK:
+ message = "VERIFY_UNLOCK";
Trace.beginSection("KeyguardViewMediator#handleMessage VERIFY_UNLOCK");
handleVerifyUnlock();
Trace.endSection();
break;
case NOTIFY_STARTED_GOING_TO_SLEEP:
+ message = "NOTIFY_STARTED_GOING_TO_SLEEP";
handleNotifyStartedGoingToSleep();
break;
case NOTIFY_FINISHED_GOING_TO_SLEEP:
+ message = "NOTIFY_FINISHED_GOING_TO_SLEEP";
handleNotifyFinishedGoingToSleep();
break;
case NOTIFY_STARTED_WAKING_UP:
+ message = "NOTIFY_STARTED_WAKING_UP";
Trace.beginSection(
"KeyguardViewMediator#handleMessage NOTIFY_STARTED_WAKING_UP");
handleNotifyStartedWakingUp();
Trace.endSection();
break;
case KEYGUARD_DONE:
+ message = "KEYGUARD_DONE";
Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE");
handleKeyguardDone();
Trace.endSection();
break;
case KEYGUARD_DONE_DRAWING:
+ message = "KEYGUARD_DONE_DRAWING";
Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE_DRAWING");
handleKeyguardDoneDrawing();
Trace.endSection();
break;
case SET_OCCLUDED:
+ message = "SET_OCCLUDED";
Trace.beginSection("KeyguardViewMediator#handleMessage SET_OCCLUDED");
handleSetOccluded(msg.arg1 != 0, msg.arg2 != 0);
Trace.endSection();
break;
case KEYGUARD_TIMEOUT:
+ message = "KEYGUARD_TIMEOUT";
synchronized (KeyguardViewMediator.this) {
doKeyguardLocked((Bundle) msg.obj);
}
break;
case DISMISS:
- final DismissMessage message = (DismissMessage) msg.obj;
- handleDismiss(message.getCallback(), message.getMessage());
+ message = "DISMISS";
+ final DismissMessage dismissMsg = (DismissMessage) msg.obj;
+ handleDismiss(dismissMsg.getCallback(), dismissMsg.getMessage());
break;
case START_KEYGUARD_EXIT_ANIM:
+ message = "START_KEYGUARD_EXIT_ANIM";
Trace.beginSection(
"KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
synchronized (KeyguardViewMediator.this) {
@@ -2451,21 +2470,25 @@
Trace.endSection();
break;
case CANCEL_KEYGUARD_EXIT_ANIM:
+ message = "CANCEL_KEYGUARD_EXIT_ANIM";
Trace.beginSection(
"KeyguardViewMediator#handleMessage CANCEL_KEYGUARD_EXIT_ANIM");
handleCancelKeyguardExitAnimation();
Trace.endSection();
break;
case KEYGUARD_DONE_PENDING_TIMEOUT:
+ message = "KEYGUARD_DONE_PENDING_TIMEOUT";
Trace.beginSection("KeyguardViewMediator#handleMessage"
+ " KEYGUARD_DONE_PENDING_TIMEOUT");
Log.w(TAG, "Timeout while waiting for activity drawn!");
Trace.endSection();
break;
case SYSTEM_READY:
+ message = "SYSTEM_READY";
handleSystemReady();
break;
}
+ Log.d(TAG, "KeyguardViewMediator queue processing message: " + message);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index b796334..ed1bf3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -167,9 +167,10 @@
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.LOCKSCREEN,
animator =
- getDefaultAnimatorForTransitionsToState(KeyguardState.LOCKSCREEN).apply {
- duration = 0
- }
+ getDefaultAnimatorForTransitionsToState(
+ KeyguardState.LOCKSCREEN
+ )
+ .apply { duration = 0 }
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index a8147d0..ff0db34 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -156,7 +156,12 @@
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
- interpolator = Interpolators.LINEAR
+ interpolator =
+ when (toState) {
+ KeyguardState.ALTERNATE_BOUNCER -> Interpolators.FAST_OUT_SLOW_IN
+ else -> Interpolators.LINEAR
+ }
+
duration =
when (toState) {
KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index d1ac49b..f692a39 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -49,6 +49,7 @@
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.TraceUtils.Companion.traceAsync
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -442,8 +443,10 @@
}
private suspend fun isFeatureDisabledByDevicePolicy(): Boolean =
- withContext(backgroundDispatcher) {
- devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+ traceAsync("isFeatureDisabledByDevicePolicy", TAG) {
+ withContext(backgroundDispatcher) {
+ devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+ }
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index df7c79f..45bf20d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -51,11 +52,35 @@
/** (any)->GONE transition information */
val anyStateToGoneTransition: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.to == KeyguardState.GONE }
+ repository.transitions.filter { step -> step.to == GONE }
/** (any)->AOD transition information */
val anyStateToAodTransition: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.to == KeyguardState.AOD }
+ repository.transitions.filter { step -> step.to == AOD }
+
+ /** DREAMING->(any) transition information. */
+ val fromDreamingTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.from == DREAMING }
+
+ /** (any)->Lockscreen transition information */
+ val anyStateToLockscreenTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == LOCKSCREEN }
+
+ /** (any)->Occluded transition information */
+ val anyStateToOccludedTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == OCCLUDED }
+
+ /** (any)->PrimaryBouncer transition information */
+ val anyStateToPrimaryBouncerTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == PRIMARY_BOUNCER }
+
+ /** (any)->Dreaming transition information */
+ val anyStateToDreamingTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == DREAMING }
+
+ /** (any)->AlternateBouncer transition information */
+ val anyStateToAlternateBouncerTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == ALTERNATE_BOUNCER }
/** AOD->LOCKSCREEN transition information. */
val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
@@ -64,6 +89,9 @@
val dreamingToLockscreenTransition: Flow<TransitionStep> =
repository.transition(DREAMING, LOCKSCREEN)
+ /** GONE->AOD transition information. */
+ val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD)
+
/** GONE->DREAMING transition information. */
val goneToDreamingTransition: Flow<TransitionStep> = repository.transition(GONE, DREAMING)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index ae6fc9e..0dda625 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -44,9 +44,9 @@
abstract fun start()
fun startTransitionTo(
- toState: KeyguardState,
- animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
- resetIfCancelled: Boolean = false
+ toState: KeyguardState,
+ animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
+ resetIfCancelled: Boolean = false
): UUID? {
if (
fromState != transitionInteractor.startedKeyguardState.value &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
new file mode 100644
index 0000000..bba0e37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 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.keyguard.domain.interactor
+
+import android.animation.FloatEvaluator
+import android.animation.IntEvaluator
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Encapsulates business logic for transitions between UDFPS states on the keyguard. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class UdfpsKeyguardInteractor
+@Inject
+constructor(
+ configRepo: ConfigurationRepository,
+ burnInInteractor: BurnInInteractor,
+ keyguardInteractor: KeyguardInteractor,
+) {
+ private val intEvaluator = IntEvaluator()
+ private val floatEvaluator = FloatEvaluator()
+
+ val dozeAmount = keyguardInteractor.dozeAmount
+ val scaleForResolution = configRepo.scaleForResolution
+
+ /** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */
+ val burnInOffsets: Flow<BurnInOffsets> =
+ combine(
+ keyguardInteractor.dozeAmount,
+ burnInInteractor.udfpsBurnInXOffset,
+ burnInInteractor.udfpsBurnInYOffset,
+ burnInInteractor.udfpsBurnInProgress
+ ) { dozeAmount, fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress ->
+ BurnInOffsets(
+ intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInX),
+ intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInY),
+ floatEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInProgress),
+ )
+ }
+}
+
+data class BurnInOffsets(
+ val burnInXOffset: Int, // current x burn in offset based on the aodTransitionAmount
+ val burnInYOffset: Int, // current y burn in offset based on the aodTransitionAmount
+ val burnInProgress: Float, // current progress based on the aodTransitionAmount
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt
new file mode 100644
index 0000000..ebf1beb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.adapter
+
+/**
+ * Temporary adapter class while
+ * [com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController] is being refactored
+ * before [com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy] is removed.
+ *
+ * TODO (b/278719514): Delete once udfps keyguard view is fully refactored.
+ */
+interface UdfpsKeyguardViewControllerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
new file mode 100644
index 0000000..728dd39
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsAodFingerprintViewBinder {
+
+ /**
+ * Drives UI for the UDFPS aod fingerprint view. See [UdfpsFingerprintViewBinder] and
+ * [UdfpsBackgroundViewBinder].
+ */
+ @JvmStatic
+ fun bind(
+ view: LottieAnimationView,
+ viewModel: UdfpsAodViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.burnInOffsets.collect { burnInOffsets ->
+ view.progress = burnInOffsets.burnInProgress
+ view.translationX = burnInOffsets.burnInXOffset.toFloat()
+ view.translationY = burnInOffsets.burnInYOffset.toFloat()
+ }
+ }
+
+ launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
+
+ launch {
+ viewModel.padding.collect { padding ->
+ view.setPadding(padding, padding, padding, padding)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
new file mode 100644
index 0000000..26ef468
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.binder
+
+import android.content.res.ColorStateList
+import android.widget.ImageView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsBackgroundViewBinder {
+
+ /**
+ * Drives UI for the udfps background view. See [UdfpsAodFingerprintViewBinder] and
+ * [UdfpsFingerprintViewBinder].
+ */
+ @JvmStatic
+ fun bind(
+ view: ImageView,
+ viewModel: BackgroundViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.transition.collect {
+ view.alpha = it.alpha
+ view.scaleX = it.scale
+ view.scaleY = it.scale
+ view.imageTintList = ColorStateList.valueOf(it.color)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
new file mode 100644
index 0000000..0ab8e52
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.binder
+
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.model.KeyPath
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsFingerprintViewBinder {
+ private var udfpsIconColor = 0
+
+ /**
+ * Drives UI for the UDFPS fingerprint view when it's NOT on aod. See
+ * [UdfpsAodFingerprintViewBinder] and [UdfpsBackgroundViewBinder].
+ */
+ @JvmStatic
+ fun bind(
+ view: LottieAnimationView,
+ viewModel: FingerprintViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.transition.collect {
+ view.alpha = it.alpha
+ view.scaleX = it.scale
+ view.scaleY = it.scale
+ if (udfpsIconColor != (it.color)) {
+ udfpsIconColor = it.color
+ view.invalidate()
+ }
+ }
+ }
+
+ launch {
+ viewModel.burnInOffsets.collect { burnInOffsets ->
+ view.translationX = burnInOffsets.burnInXOffset.toFloat()
+ view.translationY = burnInOffsets.burnInYOffset.toFloat()
+ }
+ }
+
+ launch {
+ viewModel.dozeAmount.collect { dozeAmount ->
+ // Lottie progress represents: aod=0 to lockscreen=1
+ view.progress = 1f - dozeAmount
+ }
+ }
+
+ launch {
+ viewModel.padding.collect { padding ->
+ view.setPadding(padding, padding, padding, padding)
+ }
+ }
+ }
+ }
+
+ // Add a callback that updates the color to `udfpsIconColor` whenever invalidate is called
+ view.addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(udfpsIconColor, PorterDuff.Mode.SRC_ATOP)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
new file mode 100644
index 0000000..b568a9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 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.biometrics.ui.binder
+
+import android.view.View
+import com.android.systemui.R
+import com.android.systemui.keyguard.ui.binder.UdfpsAodFingerprintViewBinder
+import com.android.systemui.keyguard.ui.binder.UdfpsBackgroundViewBinder
+import com.android.systemui.keyguard.ui.binder.UdfpsFingerprintViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+object UdfpsKeyguardInternalViewBinder {
+
+ @JvmStatic
+ fun bind(
+ view: View,
+ viewModel: UdfpsKeyguardInternalViewModel,
+ aodViewModel: UdfpsAodViewModel,
+ fingerprintViewModel: FingerprintViewModel,
+ backgroundViewModel: BackgroundViewModel,
+ ) {
+ view.accessibilityDelegate = viewModel.accessibilityDelegate
+
+ // bind child views
+ UdfpsAodFingerprintViewBinder.bind(view.findViewById(R.id.udfps_aod_fp), aodViewModel)
+ UdfpsFingerprintViewBinder.bind(
+ view.findViewById(R.id.udfps_lockscreen_fp),
+ fingerprintViewModel
+ )
+ UdfpsBackgroundViewBinder.bind(
+ view.findViewById(R.id.udfps_keyguard_fp_bg),
+ backgroundViewModel
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
new file mode 100644
index 0000000..667abae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.binder
+
+import android.graphics.RectF
+import android.view.View
+import android.widget.FrameLayout
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsKeyguardViewBinder {
+ /**
+ * Drives UI for the keyguard UDFPS view. Inflates child views on a background thread. For view
+ * binders for its child views, see [UdfpsFingerprintViewBinder], [UdfpsBackgroundViewBinder] &
+ * [UdfpsAodFingerprintViewBinder].
+ */
+ @JvmStatic
+ fun bind(
+ view: UdfpsKeyguardView,
+ viewModel: UdfpsKeyguardViewModel,
+ udfpsKeyguardInternalViewModel: UdfpsKeyguardInternalViewModel,
+ aodViewModel: UdfpsAodViewModel,
+ fingerprintViewModel: FingerprintViewModel,
+ backgroundViewModel: BackgroundViewModel,
+ ) {
+ view.useExpandedOverlay(viewModel.useExpandedOverlay())
+
+ val layoutInflaterFinishListener =
+ AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent ->
+ UdfpsKeyguardInternalViewBinder.bind(
+ inflatedInternalView,
+ udfpsKeyguardInternalViewModel,
+ aodViewModel,
+ fingerprintViewModel,
+ backgroundViewModel,
+ )
+ if (viewModel.useExpandedOverlay()) {
+ val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams
+ lp.width = viewModel.sensorBounds.width()
+ lp.height = viewModel.sensorBounds.height()
+ val relativeToView =
+ getBoundsRelativeToView(
+ inflatedInternalView,
+ RectF(viewModel.sensorBounds),
+ )
+ lp.setMarginsRelative(
+ relativeToView.left.toInt(),
+ relativeToView.top.toInt(),
+ relativeToView.right.toInt(),
+ relativeToView.bottom.toInt(),
+ )
+ parent!!.addView(inflatedInternalView, lp)
+ } else {
+ parent!!.addView(inflatedInternalView)
+ }
+ }
+ val inflater = AsyncLayoutInflater(view.context)
+ inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener)
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ combine(aodViewModel.isVisible, fingerprintViewModel.visible) {
+ isAodVisible,
+ isFingerprintVisible ->
+ isAodVisible || isFingerprintVisible
+ }
+ .collect { view.setVisible(it) }
+ }
+ }
+ }
+ }
+
+ /**
+ * Converts coordinates of RectF relative to the screen to coordinates relative to this view.
+ *
+ * @param bounds RectF based off screen coordinates in current orientation
+ */
+ private fun getBoundsRelativeToView(view: View, bounds: RectF): RectF {
+ val pos: IntArray = view.locationOnScreen
+ return RectF(
+ bounds.left - pos[0],
+ bounds.top - pos[1],
+ bounds.right - pos[0],
+ bounds.bottom - pos[1]
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
index a62f383..0077f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
@@ -19,10 +19,7 @@
import android.content.Context
import android.util.AttributeSet
-import android.view.Gravity
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.widget.FrameLayout
+import androidx.constraintlayout.widget.ConstraintLayout
import com.android.keyguard.LockIconView
import com.android.systemui.R
@@ -31,7 +28,7 @@
context: Context,
private val attrs: AttributeSet?,
) :
- FrameLayout(
+ ConstraintLayout(
context,
attrs,
) {
@@ -43,31 +40,11 @@
private fun addIndicationTextArea() {
val view = KeyguardIndicationArea(context, attrs)
- addView(
- view,
- FrameLayout.LayoutParams(
- MATCH_PARENT,
- WRAP_CONTENT,
- )
- .apply {
- gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
- bottomMargin = R.dimen.keyguard_indication_margin_bottom.dp()
- }
- )
+ addView(view)
}
private fun addLockIconView() {
val view = LockIconView(context, attrs).apply { id = R.id.lock_icon_view }
- addView(
- view,
- LayoutParams(
- WRAP_CONTENT,
- WRAP_CONTENT,
- )
- )
- }
-
- private fun Int.dp(): Int {
- return context.resources.getDimensionPixelSize(this)
+ addView(view)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt
new file mode 100644
index 0000000..baaeb60
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.view.layout
+
+import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.DisplayMetrics
+import android.view.View
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.WindowManager
+import androidx.annotation.VisibleForTesting
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import javax.inject.Inject
+
+/**
+ * Positions elements of the lockscreen to the default position.
+ *
+ * This will be the most common use case for phones in portrait mode.
+ */
+@SysUISingleton
+class DefaultLockscreenLayout
+@Inject
+constructor(
+ private val authController: AuthController,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val windowManager: WindowManager,
+ private val context: Context,
+) : LockscreenLayout {
+ override val id: String = DEFAULT
+
+ override fun layoutIndicationArea(rootView: KeyguardRootView) {
+ val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return
+
+ rootView.getConstraintSet().apply {
+ constrainWidth(indicationArea.id, MATCH_PARENT)
+ constrainHeight(indicationArea.id, WRAP_CONTENT)
+ connect(
+ indicationArea.id,
+ BOTTOM,
+ PARENT_ID,
+ BOTTOM,
+ R.dimen.keyguard_indication_margin_bottom.dp()
+ )
+ connect(indicationArea.id, START, PARENT_ID, START)
+ connect(indicationArea.id, END, PARENT_ID, END)
+ applyTo(rootView)
+ }
+ }
+
+ override fun layoutLockIcon(rootView: KeyguardRootView) {
+ val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
+ val scaleFactor: Float = authController.scaleFactor
+ val mBottomPaddingPx = R.dimen.lock_icon_margin_bottom.dp()
+ val mDefaultPaddingPx = R.dimen.lock_icon_padding.dp()
+ val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt()
+ val bounds = windowManager.currentWindowMetrics.bounds
+ val widthPixels = bounds.right.toFloat()
+ val heightPixels = bounds.bottom.toFloat()
+ val defaultDensity =
+ DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+ DisplayMetrics.DENSITY_DEFAULT.toFloat()
+ val lockIconRadiusPx = (defaultDensity * 36).toInt()
+
+ if (isUdfpsSupported) {
+ authController.udfpsLocation?.let { udfpsLocation ->
+ centerLockIcon(udfpsLocation, authController.udfpsRadius, scaledPadding, rootView)
+ }
+ } else {
+ centerLockIcon(
+ Point(
+ (widthPixels / 2).toInt(),
+ (heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
+ ),
+ lockIconRadiusPx * scaleFactor,
+ scaledPadding,
+ rootView
+ )
+ }
+ }
+
+ @VisibleForTesting
+ internal fun centerLockIcon(
+ center: Point,
+ radius: Float,
+ drawablePadding: Int,
+ rootView: KeyguardRootView,
+ ) {
+ val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
+ val lockIcon = lockIconView.findViewById<View>(R.id.lock_icon) ?: return
+ lockIcon.setPadding(drawablePadding, drawablePadding, drawablePadding, drawablePadding)
+
+ val sensorRect =
+ Rect().apply {
+ set(
+ center.x - radius.toInt(),
+ center.y - radius.toInt(),
+ center.x + radius.toInt(),
+ center.y + radius.toInt(),
+ )
+ }
+
+ rootView.getConstraintSet().apply {
+ constrainWidth(lockIconView.id, sensorRect.right - sensorRect.left)
+ constrainHeight(lockIconView.id, sensorRect.bottom - sensorRect.top)
+ connect(lockIconView.id, TOP, PARENT_ID, TOP, sensorRect.top)
+ connect(lockIconView.id, START, PARENT_ID, START, sensorRect.left)
+ applyTo(rootView)
+ }
+ }
+
+ private fun Int.dp(): Int {
+ return context.resources.getDimensionPixelSize(this)
+ }
+
+ private fun ConstraintLayout.getConstraintSet(): ConstraintSet {
+ val cs = ConstraintSet()
+ cs.clone(this)
+ return cs
+ }
+
+ companion object {
+ const val DEFAULT = "default"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt
new file mode 100644
index 0000000..9bc6302
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.view.layout
+
+import android.content.res.Configuration
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT
+import com.android.systemui.statusbar.policy.ConfigurationController
+import javax.inject.Inject
+
+/**
+ * Manages layout changes for the lockscreen.
+ *
+ * To add a layout, add an entry to the map with a unique id and call #transitionToLayout(string).
+ */
+@SysUISingleton
+class KeyguardLayoutManager
+@Inject
+constructor(
+ configurationController: ConfigurationController,
+ layouts: Set<@JvmSuppressWildcards LockscreenLayout>,
+ private val keyguardRootView: KeyguardRootView,
+) {
+ internal val layoutIdMap: Map<String, LockscreenLayout> = layouts.associateBy { it.id }
+ private var layout: LockscreenLayout? = layoutIdMap[DEFAULT]
+
+ init {
+ configurationController.addCallback(
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ layoutViews()
+ }
+ }
+ )
+ }
+
+ /**
+ * Transitions to a layout.
+ *
+ * @param layoutId
+ * @return whether the transition has succeeded.
+ */
+ fun transitionToLayout(layoutId: String): Boolean {
+ layout = layoutIdMap[layoutId] ?: return false
+ layoutViews()
+ return true
+ }
+
+ fun layoutViews() {
+ layout?.layoutViews(keyguardRootView)
+ }
+
+ companion object {
+ const val TAG = "KeyguardLayoutManager"
+ }
+}
+
+interface LockscreenLayout {
+ val id: String
+
+ fun layoutViews(rootView: KeyguardRootView) {
+ // Clear constraints.
+ ConstraintSet()
+ .apply {
+ clone(rootView)
+ knownIds.forEach { getConstraint(it).layout.copyFrom(ConstraintSet.Layout()) }
+ }
+ .applyTo(rootView)
+ layoutIndicationArea(rootView)
+ layoutLockIcon(rootView)
+ }
+ fun layoutIndicationArea(rootView: KeyguardRootView)
+ fun layoutLockIcon(rootView: KeyguardRootView)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt
new file mode 100644
index 0000000..b351ea8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.view.layout
+
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** Uses $ adb shell cmd statusbar layout <LayoutId> */
+class KeyguardLayoutManagerCommandListener
+@Inject
+constructor(
+ private val commandRegistry: CommandRegistry,
+ private val keyguardLayoutManager: KeyguardLayoutManager
+) {
+ private val layoutCommand = KeyguardLayoutManagerCommand()
+
+ fun start() {
+ commandRegistry.registerCommand(COMMAND) { layoutCommand }
+ }
+
+ internal inner class KeyguardLayoutManagerCommand : Command {
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ val arg = args.getOrNull(0)
+ if (arg == null || arg.lowercase() == "help") {
+ help(pw)
+ return
+ }
+
+ if (keyguardLayoutManager.transitionToLayout(arg)) {
+ pw.println("Transition succeeded!")
+ } else {
+ pw.println("Invalid argument! To see available layout ids, run:")
+ pw.println("$ adb shell cmd statusbar layout help")
+ }
+ }
+
+ override fun help(pw: PrintWriter) {
+ pw.println("Usage: $ adb shell cmd statusbar layout <layoutId>")
+ pw.println("Existing Layout Ids: ")
+ keyguardLayoutManager.layoutIdMap.forEach { entry -> pw.println("${entry.key}") }
+ }
+ }
+
+ companion object {
+ internal const val COMMAND = "layout"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt
new file mode 100644
index 0000000..00f93e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.view.layout
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+abstract class LockscreenLayoutModule {
+ @Binds
+ @IntoSet
+ abstract fun bindDefaultLayout(
+ defaultLockscreenLayout: DefaultLockscreenLayout
+ ): LockscreenLayout
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 9ca4bd6..e24d326 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -48,7 +48,7 @@
)
val transitionEnded =
- keyguardTransitionInteractor.dreamingToLockscreenTransition.filter { step ->
+ keyguardTransitionInteractor.fromDreamingTransition.filter { step ->
step.transitionState == TransitionState.FINISHED ||
step.transitionState == TransitionState.CANCELED
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
new file mode 100644
index 0000000..667c2f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.keyguard.domain.interactor.BurnInOffsets
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** View-model for UDFPS AOD view. */
+@ExperimentalCoroutinesApi
+class UdfpsAodViewModel
+@Inject
+constructor(
+ val interactor: UdfpsKeyguardInteractor,
+ val context: Context,
+) {
+ val alpha: Flow<Float> = interactor.dozeAmount
+ val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets
+ val isVisible: Flow<Boolean> = alpha.map { it != 0f }
+
+ // Padding between the fingerprint icon and its bounding box in pixels.
+ val padding: Flow<Int> =
+ interactor.scaleForResolution.map { scale ->
+ (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+ .roundToInt()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt
new file mode 100644
index 0000000..d894a11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import com.android.systemui.biometrics.UdfpsKeyguardAccessibilityDelegate
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardInternalViewModel
+@Inject
+constructor(val accessibilityDelegate: UdfpsKeyguardAccessibilityDelegate)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
new file mode 100644
index 0000000..929f27f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import android.graphics.Rect
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardViewModel
+@Inject
+constructor(
+ private val featureFlags: FeatureFlags,
+) {
+ var sensorBounds: Rect = Rect()
+
+ fun useExpandedOverlay(): Boolean {
+ return featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
new file mode 100644
index 0000000..098b481
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
@@ -0,0 +1,36 @@
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.graphics.Rect
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.binder.UdfpsKeyguardViewBinder
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class UdfpsKeyguardViewModels
+@Inject
+constructor(
+ private val viewModel: UdfpsKeyguardViewModel,
+ private val internalViewModel: UdfpsKeyguardInternalViewModel,
+ private val aodViewModel: UdfpsAodViewModel,
+ private val lockscreenFingerprintViewModel: FingerprintViewModel,
+ private val lockscreenBackgroundViewModel: BackgroundViewModel,
+) {
+
+ fun setSensorBounds(sensorBounds: Rect) {
+ viewModel.sensorBounds = sensorBounds
+ }
+
+ fun bindViews(view: UdfpsKeyguardView) {
+ UdfpsKeyguardViewBinder.bind(
+ view,
+ viewModel,
+ internalViewModel,
+ aodViewModel,
+ lockscreenFingerprintViewModel,
+ lockscreenBackgroundViewModel
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
new file mode 100644
index 0000000..fd4b666
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import android.content.Context
+import androidx.annotation.ColorInt
+import com.android.settingslib.Utils.getColorAttrDefaultColor
+import com.android.systemui.R
+import com.android.systemui.keyguard.domain.interactor.BurnInOffsets
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** View-model for UDFPS lockscreen views. */
+@ExperimentalCoroutinesApi
+open class UdfpsLockscreenViewModel(
+ context: Context,
+ lockscreenColorResId: Int,
+ alternateBouncerColorResId: Int,
+ transitionInteractor: KeyguardTransitionInteractor,
+) {
+ private val toLockscreen: Flow<TransitionViewModel> =
+ transitionInteractor.anyStateToLockscreenTransition.map {
+ TransitionViewModel(
+ alpha =
+ if (it.from == KeyguardState.AOD) {
+ it.value // animate
+ } else {
+ 1f
+ },
+ scale = 1f,
+ color = getColorAttrDefaultColor(context, lockscreenColorResId),
+ )
+ }
+
+ private val toAlternateBouncer: Flow<TransitionViewModel> =
+ transitionInteractor.anyStateToAlternateBouncerTransition.map {
+ TransitionViewModel(
+ alpha = 1f,
+ scale =
+ if (visibleInKeyguardState(it.from)) {
+ 1f
+ } else {
+ it.value
+ },
+ color = getColorAttrDefaultColor(context, alternateBouncerColorResId),
+ )
+ }
+
+ private val fadeOut: Flow<TransitionViewModel> =
+ merge(
+ transitionInteractor.anyStateToGoneTransition,
+ transitionInteractor.anyStateToAodTransition,
+ transitionInteractor.anyStateToOccludedTransition,
+ transitionInteractor.anyStateToPrimaryBouncerTransition,
+ transitionInteractor.anyStateToDreamingTransition,
+ )
+ .map {
+ TransitionViewModel(
+ alpha =
+ if (visibleInKeyguardState(it.from)) {
+ 1f - it.value
+ } else {
+ 0f
+ },
+ scale = 1f,
+ color =
+ if (it.from == KeyguardState.ALTERNATE_BOUNCER) {
+ getColorAttrDefaultColor(context, alternateBouncerColorResId)
+ } else {
+ getColorAttrDefaultColor(context, lockscreenColorResId)
+ },
+ )
+ }
+
+ private fun visibleInKeyguardState(state: KeyguardState): Boolean {
+ return when (state) {
+ KeyguardState.OFF,
+ KeyguardState.DOZING,
+ KeyguardState.DREAMING,
+ KeyguardState.AOD,
+ KeyguardState.PRIMARY_BOUNCER,
+ KeyguardState.GONE,
+ KeyguardState.OCCLUDED -> false
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.ALTERNATE_BOUNCER -> true
+ }
+ }
+
+ val transition: Flow<TransitionViewModel> =
+ merge(
+ toAlternateBouncer,
+ toLockscreen,
+ fadeOut,
+ )
+ val visible: Flow<Boolean> = transition.map { it.alpha != 0f }
+}
+
+@ExperimentalCoroutinesApi
+class FingerprintViewModel
+@Inject
+constructor(
+ val context: Context,
+ transitionInteractor: KeyguardTransitionInteractor,
+ interactor: UdfpsKeyguardInteractor,
+) :
+ UdfpsLockscreenViewModel(
+ context,
+ android.R.attr.textColorPrimary,
+ com.android.internal.R.attr.materialColorOnPrimaryFixed,
+ transitionInteractor,
+ ) {
+ val dozeAmount: Flow<Float> = interactor.dozeAmount
+ val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets
+
+ // Padding between the fingerprint icon and its bounding box in pixels.
+ val padding: Flow<Int> =
+ interactor.scaleForResolution.map { scale ->
+ (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+ .roundToInt()
+ }
+}
+
+@ExperimentalCoroutinesApi
+class BackgroundViewModel
+@Inject
+constructor(
+ val context: Context,
+ transitionInteractor: KeyguardTransitionInteractor,
+) :
+ UdfpsLockscreenViewModel(
+ context,
+ com.android.internal.R.attr.colorSurface,
+ com.android.internal.R.attr.materialColorPrimaryFixed,
+ transitionInteractor,
+ )
+
+data class TransitionViewModel(
+ val alpha: Float,
+ val scale: Float,
+ @ColorInt val color: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
index 150de26..702a23e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
@@ -189,15 +189,15 @@
)
}
- fun logDisplayModeChanged(currentMode: Int, newMode: Int) {
+ fun logDisplaySizeChanged(currentSize: Point, newSize: Point) {
logBuffer.log(
TAG,
INFO,
{
- int1 = currentMode
- int2 = newMode
+ str1 = currentSize.flattenToString()
+ str2 = newSize.flattenToString()
},
- { "Resolution changed, deferring mode change to $int2, staying at $int1" },
+ { "Resolution changed, deferring size change to $str2, staying at $str1" },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 6b993ce..576eb9e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -716,8 +716,7 @@
val appUid = currentEntry?.appUid ?: Process.INVALID_UID
val isExplicit =
desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
- MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT &&
- mediaFlags.isExplicitIndicatorEnabled()
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
val progress =
if (mediaFlags.isResumeProgressEnabled()) {
@@ -826,12 +825,10 @@
// Explicit Indicator
var isExplicit = false
- if (mediaFlags.isExplicitIndicatorEnabled()) {
- val mediaMetadataCompat = MediaMetadataCompat.fromMediaMetadata(metadata)
- isExplicit =
- mediaMetadataCompat?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
- MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
- }
+ val mediaMetadataCompat = MediaMetadataCompat.fromMediaMetadata(metadata)
+ isExplicit =
+ mediaMetadataCompat?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
// Artist name
var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
@@ -1253,6 +1250,9 @@
return try {
val options = BroadcastOptions.makeBasic()
options.setInteractive(true)
+ options.setPendingIntentBackgroundActivityStartMode(
+ BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
intent.send(options.toBundle())
true
} catch (e: PendingIntent.CanceledException) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
index 30ee147..2883210 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
@@ -128,6 +128,15 @@
var visibilityChangedListener: ((Boolean) -> Unit)? = null
+ /**
+ * Whether the doze wake up animation is delayed and we are currently waiting for it to start.
+ */
+ var isDozeWakeUpAnimationWaiting: Boolean = false
+ set(value) {
+ field = value
+ refreshMediaPosition()
+ }
+
/** single pane media container placed at the top of the notifications list */
var singlePaneContainer: MediaContainerView? = null
private set
@@ -221,7 +230,13 @@
// by the clock. This is not the case for single-line clock though.
// For single shade, we don't need to do it, because media is a child of NSSL, which already
// gets hidden on AOD.
- return !statusBarStateController.isDozing
+ // Media also has to be hidden when waking up from dozing, and the doze wake up animation is
+ // delayed and waiting to be started.
+ // This is to stay in sync with the delaying of the horizontal alignment of the rest of the
+ // keyguard container, that is also delayed until the "wait" is over.
+ // If we show media during this waiting period, the shade will still be centered, and using
+ // the entire width of the screen, and making media show fully stretched.
+ return !statusBarStateController.isDozing && !isDozeWakeUpAnimationWaiting
}
private fun showMediaPlayer() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 35082fd..a978b92 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -23,6 +23,7 @@
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
+import android.app.ActivityOptions;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.app.WallpaperColors;
@@ -535,7 +536,10 @@
mLockscreenUserManager.getCurrentUserId());
if (showOverLockscreen) {
try {
- clickIntent.send();
+ ActivityOptions opts = ActivityOptions.makeBasic();
+ opts.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ clickIntent.send(opts.toBundle());
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Pending intent for " + key + " was cancelled");
}
@@ -684,6 +688,8 @@
try {
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
deviceIntent.send(options.toBundle());
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Device pending intent was canceled");
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 9bc66f6..01f047c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -43,9 +43,6 @@
*/
fun areNearbyMediaDevicesEnabled() = featureFlags.isEnabled(Flags.MEDIA_NEARBY_DEVICES)
- /** Check whether we show explicit indicator on UMO */
- fun isExplicitIndicatorEnabled() = featureFlags.isEnabled(Flags.MEDIA_EXPLICIT_INDICATOR)
-
/**
* If true, keep active media controls for the lifetime of the MediaSession, regardless of
* whether the underlying notification was dismissed
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 318cd99..26a7d04 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -20,6 +20,7 @@
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE;
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
+import android.annotation.DrawableRes;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -181,27 +182,23 @@
mController.getSelectedMediaDevice(), device)));
boolean isHost = device.isHostForOngoingSession()
&& isActiveWithOngoingSession;
- if (isHost) {
+ if (isActiveWithOngoingSession) {
mCurrentActivePosition = position;
updateTitleIcon(R.drawable.media_output_icon_volume,
mController.getColorItemContent());
mSubTitleText.setText(device.getSubtextString());
updateTwoLineLayoutContentAlpha(DEVICE_CONNECTED_ALPHA);
- updateEndClickAreaAsSessionEditing(device);
+ updateEndClickAreaAsSessionEditing(device,
+ isHost ? R.drawable.media_output_status_edit_session
+ : R.drawable.ic_sound_bars_anim);
setTwoLineLayout(device, null /* title */, true /* bFocused */,
true /* showSeekBar */, false /* showProgressBar */,
true /* showSubtitle */, false /* showStatus */,
true /* showEndTouchArea */, false /* isFakeActive */);
initSeekbar(device, isCurrentSeekbarInvisible);
} else {
- if (isActiveWithOngoingSession) {
- //Selected device which has ongoing session, disable seekbar since we
- //only allow volume control on Host
+ if (currentlyConnected) {
mCurrentActivePosition = position;
- }
- boolean showSeekbar =
- (!device.hasOngoingSession() && currentlyConnected);
- if (showSeekbar) {
updateTitleIcon(R.drawable.media_output_icon_volume,
mController.getColorItemContent());
initSeekbar(device, isCurrentSeekbarInvisible);
@@ -222,10 +219,10 @@
updateClickActionBasedOnSelectionBehavior(device)
? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA);
setTwoLineLayout(device, currentlyConnected /* bFocused */,
- showSeekbar /* showSeekBar */,
+ currentlyConnected /* showSeekBar */,
false /* showProgressBar */, true /* showSubtitle */,
deviceStatusIcon != null /* showStatus */,
- isActiveWithOngoingSession /* isFakeActive */);
+ false /* isFakeActive */);
}
} else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
setUpDeviceIcon(device);
@@ -267,25 +264,16 @@
setSingleLineLayout(getItemTitle(device));
} else if (device.hasOngoingSession()) {
mCurrentActivePosition = position;
- if (device.isHostForOngoingSession()) {
- updateTitleIcon(R.drawable.media_output_icon_volume,
- mController.getColorItemContent());
- updateEndClickAreaAsSessionEditing(device);
- mEndClickIcon.setVisibility(View.VISIBLE);
- setSingleLineLayout(getItemTitle(device), true /* showSeekBar */,
- false /* showProgressBar */, false /* showCheckBox */,
- true /* showEndTouchArea */);
- initSeekbar(device, isCurrentSeekbarInvisible);
- } else {
- updateDeviceStatusIcon(mContext.getDrawable(
- R.drawable.ic_sound_bars_anim));
- mStatusIcon.setVisibility(View.VISIBLE);
- updateSingleLineLayoutContentAlpha(
- updateClickActionBasedOnSelectionBehavior(device)
- ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA);
- setSingleLineLayout(getItemTitle(device));
- initFakeActiveDevice();
- }
+ updateTitleIcon(R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
+ updateEndClickAreaAsSessionEditing(device, device.isHostForOngoingSession()
+ ? R.drawable.media_output_status_edit_session
+ : R.drawable.ic_sound_bars_anim);
+ mEndClickIcon.setVisibility(View.VISIBLE);
+ setSingleLineLayout(getItemTitle(device), true /* showSeekBar */,
+ false /* showProgressBar */, false /* showCheckBox */,
+ true /* showEndTouchArea */);
+ initSeekbar(device, isCurrentSeekbarInvisible);
} else if (mController.isCurrentConnectedDeviceRemote()
&& !mController.getSelectableMediaDevice().isEmpty()) {
//If device is connected and there's other selectable devices, layout as
@@ -362,7 +350,7 @@
mStatusIcon.setAlpha(alphaValue);
}
- private void updateEndClickAreaAsSessionEditing(MediaDevice device) {
+ private void updateEndClickAreaAsSessionEditing(MediaDevice device, @DrawableRes int id) {
mEndClickIcon.setOnClickListener(null);
mEndTouchArea.setOnClickListener(null);
updateEndClickAreaColor(mController.getColorSeekbarProgress());
@@ -371,6 +359,11 @@
mEndClickIcon.setOnClickListener(
v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v));
mEndTouchArea.setOnClickListener(v -> mEndClickIcon.performClick());
+ Drawable drawable = mContext.getDrawable(id);
+ mEndClickIcon.setImageDrawable(drawable);
+ if (drawable instanceof AnimatedVectorDrawable) {
+ ((AnimatedVectorDrawable) drawable).start();
+ }
}
public void updateEndClickAreaColor(int color) {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
new file mode 100644
index 0000000..3c50127
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import javax.inject.Inject
+
+@SysUISingleton
+class MediaProjectionTaskSwitcherCoreStartable
+@Inject
+constructor(
+ private val notificationCoordinator: TaskSwitcherNotificationCoordinator,
+ private val featureFlags: FeatureFlags,
+) : CoreStartable {
+
+ override fun start() {
+ if (featureFlags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)) {
+ notificationCoordinator.start()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
new file mode 100644
index 0000000..22ad07e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher
+
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface MediaProjectionTaskSwitcherModule {
+
+ @Binds fun mediaRepository(impl: MediaProjectionManagerRepository): MediaProjectionRepository
+
+ @Binds fun tasksRepository(impl: ActivityTaskManagerTasksRepository): TasksRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
new file mode 100644
index 0000000..9938f11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.data.model
+
+import android.app.TaskInfo
+
+/** Represents the state of media projection. */
+sealed interface MediaProjectionState {
+ object NotProjecting : MediaProjectionState
+ object EntireScreen : MediaProjectionState
+ data class SingleTask(val task: TaskInfo) : MediaProjectionState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
new file mode 100644
index 0000000..492d482
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager
+import android.app.TaskStackListener
+import android.os.IBinder
+import android.util.Log
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.withContext
+
+/** Implementation of [TasksRepository] that uses [ActivityTaskManager] as the data source. */
+@SysUISingleton
+class ActivityTaskManagerTasksRepository
+@Inject
+constructor(
+ private val activityTaskManager: ActivityTaskManager,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : TasksRepository {
+
+ override suspend fun findRunningTaskFromWindowContainerToken(
+ windowContainerToken: IBinder
+ ): RunningTaskInfo? =
+ getRunningTasks().firstOrNull { taskInfo ->
+ taskInfo.token.asBinder() == windowContainerToken
+ }
+
+ private suspend fun getRunningTasks(): List<RunningTaskInfo> =
+ withContext(backgroundDispatcher) { activityTaskManager.getTasks(Integer.MAX_VALUE) }
+
+ override val foregroundTask: Flow<RunningTaskInfo> =
+ conflatedCallbackFlow {
+ val listener =
+ object : TaskStackListener() {
+ override fun onTaskMovedToFront(taskInfo: RunningTaskInfo) {
+ Log.d(TAG, "onTaskMovedToFront: $taskInfo")
+ trySendWithFailureLogging(taskInfo, TAG)
+ }
+ }
+ activityTaskManager.registerTaskStackListener(listener)
+ awaitClose { activityTaskManager.unregisterTaskStackListener(listener) }
+ }
+ .shareIn(applicationScope, SharingStarted.Lazily, replay = 1)
+
+ companion object {
+ private const val TAG = "TasksRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
new file mode 100644
index 0000000..38d4e69
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.util.Log
+import android.view.ContentRecordingSession
+import android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class MediaProjectionManagerRepository
+@Inject
+constructor(
+ private val mediaProjectionManager: MediaProjectionManager,
+ @Main private val handler: Handler,
+ @Application private val applicationScope: CoroutineScope,
+ private val tasksRepository: TasksRepository,
+) : MediaProjectionRepository {
+
+ override val mediaProjectionState: Flow<MediaProjectionState> =
+ conflatedCallbackFlow {
+ val callback =
+ object : MediaProjectionManager.Callback() {
+ override fun onStart(info: MediaProjectionInfo?) {
+ Log.d(TAG, "MediaProjectionManager.Callback#onStart")
+ trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+ }
+
+ override fun onStop(info: MediaProjectionInfo?) {
+ Log.d(TAG, "MediaProjectionManager.Callback#onStop")
+ trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+ }
+
+ override fun onRecordingSessionSet(
+ info: MediaProjectionInfo,
+ session: ContentRecordingSession?
+ ) {
+ Log.d(TAG, "MediaProjectionManager.Callback#onSessionStarted: $session")
+ launch { trySendWithFailureLogging(stateForSession(session), TAG) }
+ }
+ }
+ mediaProjectionManager.addCallback(callback, handler)
+ awaitClose { mediaProjectionManager.removeCallback(callback) }
+ }
+ .shareIn(scope = applicationScope, started = SharingStarted.Lazily, replay = 1)
+
+ private suspend fun stateForSession(session: ContentRecordingSession?): MediaProjectionState {
+ if (session == null) {
+ return MediaProjectionState.NotProjecting
+ }
+ if (session.contentToRecord == RECORD_CONTENT_DISPLAY || session.tokenToRecord == null) {
+ return MediaProjectionState.EntireScreen
+ }
+ val matchingTask =
+ tasksRepository.findRunningTaskFromWindowContainerToken(session.tokenToRecord)
+ ?: return MediaProjectionState.EntireScreen
+ return MediaProjectionState.SingleTask(matchingTask)
+ }
+
+ companion object {
+ private const val TAG = "MediaProjectionMngrRepo"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
new file mode 100644
index 0000000..5bec692
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.data.repository
+
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import kotlinx.coroutines.flow.Flow
+
+/** Represents a repository to retrieve and change data related to media projection. */
+interface MediaProjectionRepository {
+
+ /** Represents the current [MediaProjectionState]. */
+ val mediaProjectionState: Flow<MediaProjectionState>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt
new file mode 100644
index 0000000..544eb6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * No-op implementation of [MediaProjectionRepository] that does nothing. Currently used as a
+ * placeholder, while the real implementation is not completed.
+ */
+@SysUISingleton
+class NoOpMediaProjectionRepository @Inject constructor() : MediaProjectionRepository {
+
+ override val mediaProjectionState: Flow<MediaProjectionState> = emptyFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt
new file mode 100644
index 0000000..6a535e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.os.IBinder
+import kotlinx.coroutines.flow.Flow
+
+/** Repository responsible for retrieving data related to running tasks. */
+interface TasksRepository {
+
+ /**
+ * Tries to find a [RunningTaskInfo] with a matching window container token. Returns `null` when
+ * no matching task was found.
+ */
+ suspend fun findRunningTaskFromWindowContainerToken(
+ windowContainerToken: IBinder
+ ): RunningTaskInfo?
+
+ /**
+ * Emits a stream of [RunningTaskInfo] that have been moved to the foreground.
+ *
+ * Note: when subscribing for the first time, it will not immediately emit the current
+ * foreground task. Only after a change in foreground task has occurred.
+ */
+ val foregroundTask: Flow<RunningTaskInfo>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
new file mode 100644
index 0000000..fc5cf7d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.domain.interactor
+
+import android.app.TaskInfo
+import android.content.Intent
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Interactor with logic related to task switching in the context of media projection. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class TaskSwitchInteractor
+@Inject
+constructor(
+ mediaProjectionRepository: MediaProjectionRepository,
+ private val tasksRepository: TasksRepository,
+) {
+
+ /**
+ * Emits a stream of changes to the state of task switching, in the context of media projection.
+ */
+ val taskSwitchChanges: Flow<TaskSwitchState> =
+ mediaProjectionRepository.mediaProjectionState.flatMapLatest { projectionState ->
+ Log.d(TAG, "MediaProjectionState -> $projectionState")
+ when (projectionState) {
+ is MediaProjectionState.SingleTask -> {
+ val projectedTask = projectionState.task
+ tasksRepository.foregroundTask.map { foregroundTask ->
+ if (hasForegroundTaskSwitched(projectedTask, foregroundTask)) {
+ TaskSwitchState.TaskSwitched(projectedTask, foregroundTask)
+ } else {
+ TaskSwitchState.TaskUnchanged
+ }
+ }
+ }
+ is MediaProjectionState.EntireScreen,
+ is MediaProjectionState.NotProjecting -> {
+ flowOf(TaskSwitchState.NotProjectingTask)
+ }
+ }
+ }
+
+ /**
+ * Returns whether tasks have been switched.
+ *
+ * Always returns `false` when launcher is in the foreground. The reason is that when going to
+ * recents to switch apps, launcher becomes the new foreground task, and we don't want to show
+ * the notification then.
+ */
+ private fun hasForegroundTaskSwitched(projectedTask: TaskInfo, foregroundTask: TaskInfo) =
+ projectedTask.taskId != foregroundTask.taskId && !foregroundTask.isLauncher
+
+ private val TaskInfo.isLauncher
+ get() =
+ baseIntent.hasCategory(Intent.CATEGORY_HOME) && baseIntent.action == Intent.ACTION_MAIN
+
+ companion object {
+ private const val TAG = "TaskSwitchInteractor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt
new file mode 100644
index 0000000..cd1258e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.domain.model
+
+import android.app.TaskInfo
+
+/** Represents tha state of task switching in the context of single task media projection. */
+sealed interface TaskSwitchState {
+ /** Currently no task is being projected. */
+ object NotProjectingTask : TaskSwitchState
+ /** The foreground task is the same as the task that is currently being projected. */
+ object TaskUnchanged : TaskSwitchState
+ /** The foreground task is a different one to the task it currently being projected. */
+ data class TaskSwitched(val projectedTask: TaskInfo, val foregroundTask: TaskInfo) :
+ TaskSwitchState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
new file mode 100644
index 0000000..a4f4076
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.ui
+
+import android.content.Context
+import android.util.Log
+import android.widget.Toast
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.NotShowing
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.Showing
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.launch
+
+/** Coordinator responsible for showing/hiding the task switcher notification. */
+@SysUISingleton
+class TaskSwitcherNotificationCoordinator
+@Inject
+constructor(
+ private val context: Context,
+ @Application private val applicationScope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ private val viewModel: TaskSwitcherNotificationViewModel,
+) {
+
+ fun start() {
+ applicationScope.launch {
+ viewModel.uiState.flowOn(mainDispatcher).collect { uiState ->
+ Log.d(TAG, "uiState -> $uiState")
+ when (uiState) {
+ is Showing -> showNotification(uiState)
+ is NotShowing -> hideNotification()
+ }
+ }
+ }
+ }
+
+ private fun showNotification(uiState: Showing) {
+ val text =
+ """
+ Sharing pauses when you switch apps.
+ Share this app instead.
+ Switch back.
+ """
+ .trimIndent()
+ // TODO(b/286201515): Create actual notification.
+ Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
+ }
+
+ private fun hideNotification() {}
+
+ companion object {
+ private const val TAG = "TaskSwitchNotifCoord"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt
new file mode 100644
index 0000000..21aee72
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.ui.model
+
+import android.app.TaskInfo
+
+/** Represents the UI state for the task switcher notification. */
+sealed interface TaskSwitcherNotificationUiState {
+ /** The notification should not be shown. */
+ object NotShowing : TaskSwitcherNotificationUiState
+ /** The notification should be shown. */
+ data class Showing(
+ val projectedTask: TaskInfo,
+ val foregroundTask: TaskInfo,
+ ) : TaskSwitcherNotificationUiState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
new file mode 100644
index 0000000..d9754d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.ui.viewmodel
+
+import android.util.Log
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class TaskSwitcherNotificationViewModel @Inject constructor(interactor: TaskSwitchInteractor) {
+
+ val uiState: Flow<TaskSwitcherNotificationUiState> =
+ interactor.taskSwitchChanges.map { taskSwitchChange ->
+ Log.d(TAG, "taskSwitchChange: $taskSwitchChange")
+ when (taskSwitchChange) {
+ is TaskSwitchState.TaskSwitched -> {
+ TaskSwitcherNotificationUiState.Showing(
+ projectedTask = taskSwitchChange.projectedTask,
+ foregroundTask = taskSwitchChange.foregroundTask,
+ )
+ }
+ is TaskSwitchState.NotProjectingTask,
+ is TaskSwitchState.TaskUnchanged -> {
+ TaskSwitcherNotificationUiState.NotShowing
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "TaskSwitchNotifVM"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
index 63276fe..99daf36 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
@@ -28,18 +28,21 @@
import android.os.PatternMatcher;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -64,22 +67,21 @@
private Context mCurrentUserContext;
private final IOverlayManager mOverlayManager;
private final Executor mUiBgExecutor;
+ private final UserTracker mUserTracker;
private ArrayList<ModeChangedListener> mListeners = new ArrayList<>();
- private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback =
- new DeviceProvisionedController.DeviceProvisionedListener() {
- @Override
- public void onUserSwitched() {
- if (DEBUG) {
- Log.d(TAG, "onUserSwitched: "
- + ActivityManagerWrapper.getInstance().getCurrentUserId());
- }
+ private final UserTracker.Callback mUserTrackerCallback = new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ if (DEBUG) {
+ Log.d(TAG, "onUserChanged: "
+ + newUser);
+ }
- // Update the nav mode for the current user
- updateCurrentInteractionMode(true /* notify */);
- }
- };
+ updateCurrentInteractionMode(true /* notify */);
+ }
+ };
// The primary user SysUI process doesn't get AppInfo changes from overlay package changes for
// the secondary user (b/158613864), so we need to update the interaction mode here as well
@@ -97,19 +99,20 @@
@Inject
public NavigationModeController(Context context,
- DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,
+ UserTracker userTracker,
+ @Main Executor mainExecutor,
@UiBackground Executor uiBgExecutor,
DumpManager dumpManager) {
mContext = context;
mCurrentUserContext = context;
+ mUserTracker = userTracker;
+ mUserTracker.addCallback(mUserTrackerCallback, mainExecutor);
mOverlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
mUiBgExecutor = uiBgExecutor;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
- deviceProvisionedController.addCallback(mDeviceProvisionedCallback);
-
IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
overlayFilter.addDataScheme("package");
overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
@@ -129,6 +132,7 @@
}
public void updateCurrentInteractionMode(boolean notify) {
+ Trace.beginSection("NMC#updateCurrentInteractionMode");
mCurrentUserContext = getCurrentUserContext();
int mode = getCurrentInteractionMode(mCurrentUserContext);
mUiBgExecutor.execute(() ->
@@ -144,6 +148,7 @@
mListeners.get(i).onNavigationModeChanged(mode);
}
}
+ Trace.endSection();
}
public int addListener(ModeChangedListener listener) {
@@ -171,7 +176,7 @@
}
public Context getCurrentUserContext() {
- int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
+ int userId = mUserTracker.getUserId();
if (DEBUG) {
Log.d(TAG, "getCurrentUserContext: contextUser=" + mContext.getUserId()
+ " currentUser=" + userId);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 2a9e7d0..1ca2a96 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -330,7 +330,9 @@
final int eventId = mClickEventId++;
mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
eventId);
- mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget();
+ if (!mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+ mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget();
+ }
}
public LogMaker populate(LogMaker logMaker) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
index 3aefcb3..7e234ae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
@@ -71,6 +71,8 @@
ActivityOptions opts = ActivityOptions.makeBasic();
opts.setDisallowEnterPictureInPictureWhileLaunching(
intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
+ opts.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
try {
actionIntent.send(context, 0, null, null, null, null, opts.toBundle());
if (intent.getBooleanExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, false)) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
index 13678b0..9e8ea3a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
@@ -99,6 +99,8 @@
try {
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
+ options.setPendingIntentBackgroundActivityStartMode(
+ BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
intent.send(options.toBundle());
finisher.run();
} catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
index 9761f59..ef58b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
@@ -55,7 +55,8 @@
Log.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent());
}
ActivityOptions opts = ActivityOptions.makeBasic();
-
+ opts.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
try {
pendingIntent.send(context, 0, fillIn, null, null, null, opts.toBundle());
} catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 8879501..5199bd4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -84,6 +84,7 @@
window.getDecorView();
window.setLayout(
WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
+ getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false);
setContentView(R.layout.brightness_mirror_container);
FrameLayout frame = findViewById(R.id.brightness_mirror_container);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 5c776f5..d97db3b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -117,6 +117,7 @@
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeLog;
@@ -208,7 +209,6 @@
import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
import com.android.systemui.statusbar.phone.TapAgainViewController;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
@@ -238,7 +238,7 @@
import kotlinx.coroutines.CoroutineDispatcher;
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
public final class NotificationPanelViewController implements ShadeSurface, Dumpable {
public static final String TAG = NotificationPanelView.class.getSimpleName();
@@ -1175,6 +1175,7 @@
mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
mKeyguardStatusViewController.init();
+ mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
updateClockAppearance();
if (mKeyguardUserSwitcherController != null) {
@@ -1227,6 +1228,7 @@
private void onSplitShadeEnabledChanged() {
mShadeLog.logSplitShadeChanged(mSplitShadeEnabled);
+ mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
// Reset any left over overscroll state. It is a rare corner case but can happen.
mQsController.setOverScrollAmount(0);
mScrimController.setNotificationsOverScrollAmount(0);
@@ -1407,11 +1409,13 @@
mKeyguardBottomArea = keyguardBottomArea;
}
- void setOpenCloseListener(OpenCloseListener openCloseListener) {
+ @Override
+ public void setOpenCloseListener(OpenCloseListener openCloseListener) {
mOpenCloseListener = openCloseListener;
}
- void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
+ @Override
+ public void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
mTrackingStartedListener = trackingStartedListener;
}
@@ -1623,6 +1627,7 @@
mWillPlayDelayedDozeAmountAnimation = willPlay;
mWakeUpCoordinator.logDelayingClockWakeUpAnimation(willPlay);
+ mKeyguardMediaController.setDozeWakeUpAnimationWaiting(willPlay);
// Once changing this value, see if we should move the clock.
positionClockAndNotifications();
@@ -3378,11 +3383,13 @@
ViewGroupFadeHelper.reset(mView);
}
- void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+ @Override
+ public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
mView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
}
- void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+ @Override
+ public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
@@ -3852,8 +3859,8 @@
return !isFullyCollapsed() && !mTracking && !mClosing;
}
- /** Collapses the shade instantly without animation. */
- void instantCollapse() {
+ @Override
+ public void instantCollapse() {
abortAnimations();
setExpandedFraction(0f);
if (mExpanding) {
@@ -4026,8 +4033,8 @@
mFixedDuration = NO_FIXED_DURATION;
}
- /** */
- boolean postToView(Runnable action) {
+ @Override
+ public boolean postToView(Runnable action) {
return mView.post(action);
}
@@ -5122,18 +5129,5 @@
return super.performAccessibilityAction(host, action, args);
}
}
-
- /** Listens for when touch tracking begins. */
- interface TrackingStartedListener {
- void onTrackingStarted();
- }
-
- /** Listens for when shade begins opening of finishes closing. */
- interface OpenCloseListener {
- /** Called when the shade finishes closing. */
- void onClosingFinished();
- /** Called when the shade starts opening. */
- void onOpenStarted();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 1361c9f..025c4611 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -68,6 +68,7 @@
import com.android.systemui.R;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
@@ -98,7 +99,6 @@
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.LargeScreenUtils;
@@ -113,7 +113,7 @@
/** Handles QuickSettings touch handling, expansion and animation state
* TODO (b/264460656) make this dumpable
*/
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
public class QuickSettingsController implements Dumpable {
public static final String TAG = "QuickSettingsController";
@@ -1220,14 +1220,15 @@
if (mIsFullWidth) {
clipStatusView = qsVisible;
float screenCornerRadius =
- !mSplitShadeEnabled || mRecordingController.isRecording()
- || mCastController.hasConnectedCastDevice()
+ mRecordingController.isRecording() || mCastController.hasConnectedCastDevice()
? 0 : mScreenCornerRadius;
radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
Math.min(top / (float) mScrimCornerRadius, 1f));
- float bottomRadius = mExpanded ? screenCornerRadius :
- calculateBottomCornerRadius(screenCornerRadius);
+ float bottomRadius = mSplitShadeEnabled ? screenCornerRadius : 0;
+ if (!mExpanded) {
+ bottomRadius = calculateBottomCornerRadius(bottomRadius);
+ }
mScrimController.setNotificationBottomRadius(bottomRadius);
}
if (isQsFragmentCreated()) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index 9ed0e9a..317d885 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -165,8 +165,7 @@
NotificationShadeWindowViewController notificationShadeWindowViewController);
/** */
- void setNotificationPanelViewController(
- NotificationPanelViewController notificationPanelViewController);
+ void setShadeViewController(ShadeViewController shadeViewController);
/** Listens for shade visibility changes. */
interface ShadeVisibilityListener {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index c9338b3..b92afac 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -70,7 +70,8 @@
private boolean mExpandedVisible;
- private NotificationPanelViewController mNotificationPanelViewController;
+ // TODO(b/237661616): Rename this variable to mShadeViewController.
+ private ShadeViewController mNotificationPanelViewController;
private NotificationPresenter mPresenter;
private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
private ShadeVisibilityListener mShadeVisibilityListener;
@@ -426,12 +427,11 @@
}
@Override
- public void setNotificationPanelViewController(
- NotificationPanelViewController notificationPanelViewController) {
- mNotificationPanelViewController = notificationPanelViewController;
+ public void setShadeViewController(ShadeViewController shadeViewController) {
+ mNotificationPanelViewController = shadeViewController;
mNotificationPanelViewController.setTrackingStartedListener(this::runPostCollapseRunnables);
mNotificationPanelViewController.setOpenCloseListener(
- new NotificationPanelViewController.OpenCloseListener() {
+ new OpenCloseListener() {
@Override
public void onClosingFinished() {
ShadeControllerImpl.this.onClosingFinished();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 0154b49..8ae9e5e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -72,6 +72,12 @@
@ClassKey(AuthRippleController::class)
abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable
+ @Binds
+ @SysUISingleton
+ abstract fun bindsShadeViewController(
+ notificationPanelViewController: NotificationPanelViewController
+ ): ShadeViewController
+
companion object {
const val SHADE_HEADER = "large_screen_shade_header"
@@ -209,9 +215,15 @@
@Provides
@SysUISingleton
fun providesLockIconView(
- notificationShadeWindowView: NotificationShadeWindowView,
+ keyguardRootView: KeyguardRootView,
+ notificationPanelView: NotificationPanelView,
+ featureFlags: FeatureFlags
): LockIconView {
- return notificationShadeWindowView.findViewById(R.id.lock_icon_view)
+ if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+ return keyguardRootView.findViewById(R.id.lock_icon_view)
+ } else {
+ return notificationPanelView.findViewById(R.id.lock_icon_view)
+ }
}
// TODO(b/277762009): Only allow this view's controller to inject the view. See above.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 3d9fcf9..9aa5eb0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -17,6 +17,7 @@
import android.view.MotionEvent
import android.view.ViewGroup
+import android.view.ViewTreeObserver
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.statusbar.RemoteInputController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -77,6 +78,9 @@
/** Collapses the shade with an animation duration in milliseconds. */
fun collapseWithDuration(animationDuration: Int)
+ /** Collapses the shade instantly without animation. */
+ fun instantCollapse()
+
/**
* Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
* in split shade, it will collapse the whole shade.
@@ -100,6 +104,9 @@
/** Returns whether the shade's top level view is enabled. */
val isViewEnabled: Boolean
+ /** Sets a listener to be notified when the shade starts opening or finishes closing. */
+ fun setOpenCloseListener(openCloseListener: OpenCloseListener)
+
/** Returns whether status bar icons should be hidden when the shade is expanded. */
fun shouldHideStatusBarIconsWhenExpanded(): Boolean
@@ -109,6 +116,9 @@
*/
fun blockExpansionForCurrentTouch()
+ /** Sets a listener to be notified when touch tracking begins. */
+ fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener)
+
/**
* Disables the shade header.
*
@@ -178,6 +188,15 @@
/** Ensures that the touchable region is updated. */
fun updateTouchableRegion()
+ /** Adds a global layout listener. */
+ fun addOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener)
+
+ /** Removes a global layout listener. */
+ fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener)
+
+ /** Posts the given runnable to the view. */
+ fun postToView(action: Runnable): Boolean
+
// ******* Begin Keyguard Section *********
/** Animate to expanded shade after a delay in ms. Used for lockscreen to shade transition. */
fun transitionToExpandedShade(delay: Long)
@@ -337,3 +356,17 @@
/** Return the fraction of the shade that's expanded, when in lockscreen. */
val lockscreenShadeDragProgress: Float
}
+
+/** Listens for when touch tracking begins. */
+interface TrackingStartedListener {
+ fun onTrackingStarted()
+}
+
+/** Listens for when shade begins opening or finishes closing. */
+interface OpenCloseListener {
+ /** Called when the shade finishes closing. */
+ fun onClosingFinished()
+
+ /** Called when the shade starts opening. */
+ fun onOpenStarted()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index e6e3e7e..ea5ca27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,6 +19,7 @@
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import android.app.ActivityOptions;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.admin.DevicePolicyManager;
@@ -158,7 +159,11 @@
final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
if (intentSender != null) {
try {
- mContext.startIntentSender(intentSender, null, 0, 0, 0);
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ mContext.startIntentSender(intentSender, null, 0, 0, 0,
+ options.toBundle());
} catch (IntentSender.SendIntentException e) {
/* ignore */
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 075b41b..035fa04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -41,6 +41,8 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeSurface;
import com.android.systemui.shade.carrier.ShadeCarrierGroupController;
import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
@@ -273,6 +275,21 @@
return ongoingCallController;
}
+ /**
+ * {@link NotificationPanelViewController} implements two interfaces:
+ * - {@link com.android.systemui.shade.ShadeViewController}, which can be used by any class
+ * needing access to the shade.
+ * - {@link ShadeSurface}, which should *only* be used by {@link CentralSurfacesImpl}.
+ *
+ * Since {@link ShadeSurface} should only be accessible by {@link CentralSurfacesImpl}, it's
+ * *only* bound in this CentralSurfaces dependencies module.
+ * The {@link com.android.systemui.shade.ShadeViewController} interface is bound in
+ * {@link com.android.systemui.shade.ShadeModule} so others can access it.
+ */
+ @Binds
+ @SysUISingleton
+ ShadeSurface provideShadeSurface(NotificationPanelViewController impl);
+
/** */
@Binds
ShadeCarrierGroupController.SlotIndexResolver provideSlotIndexResolver(
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 8778463..11b1053 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -124,6 +124,7 @@
private var showSensitiveContentForCurrentUser = false
private var showSensitiveContentForManagedUser = false
private var managedUserHandle: UserHandle? = null
+ private var mSplitShadeEnabled = false
// TODO(b/202758428): refactor so that we can test color updates via region samping, similar to
// how we test color updates when theme changes (See testThemeChangeUpdatesTextColor).
@@ -131,6 +132,7 @@
// TODO: Move logic into SmartspaceView
var stateChangeListener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
+ (v as SmartspaceView).setSplitShadeEnabled(mSplitShadeEnabled)
smartspaceViews.add(v as SmartspaceView)
connectSession()
@@ -221,6 +223,11 @@
execution.assertIsMainThread()
smartspaceViews.forEach { it.setDozeAmount(eased) }
}
+
+ override fun onDozingChanged(isDozing: Boolean) {
+ execution.assertIsMainThread()
+ smartspaceViews.forEach { it.setDozing(isDozing) }
+ }
}
private val deviceProvisionedListener =
@@ -426,6 +433,11 @@
reloadSmartspace()
}
+ fun setSplitShadeEnabled(enabled: Boolean) {
+ mSplitShadeEnabled = enabled
+ smartspaceViews.forEach { it.setSplitShadeEnabled(enabled) }
+ }
+
/**
* Requests the smartspace session for an update.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 2fa070c..07eb8a00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -28,12 +28,9 @@
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.expansionChanges
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
@@ -50,30 +47,29 @@
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.io.PrintWriter
import javax.inject.Inject
-import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.emitAll
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
/**
* Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
- * headers on the lockscreen.
+ * headers on the lockscreen. If enabled, it will also track and hide seen notifications on the
+ * lockscreen.
*/
@CoordinatorScope
class KeyguardCoordinator
@@ -86,7 +82,6 @@
private val keyguardRepository: KeyguardRepository,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val logger: KeyguardCoordinatorLogger,
- private val notifPipelineFlags: NotifPipelineFlags,
@Application private val scope: CoroutineScope,
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
private val secureSettings: SecureSettings,
@@ -95,6 +90,8 @@
) : Coordinator, Dumpable {
private val unseenNotifications = mutableSetOf<NotificationEntry>()
+ private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
+ private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
private var unseenFilterEnabled = false
override fun attach(pipeline: NotifPipeline) {
@@ -109,79 +106,130 @@
private fun attachUnseenFilter(pipeline: NotifPipeline) {
pipeline.addFinalizeFilter(unseenNotifFilter)
pipeline.addCollectionListener(collectionListener)
- scope.launch { trackUnseenNotificationsWhileUnlocked() }
- scope.launch { invalidateWhenUnseenSettingChanges() }
+ scope.launch { trackUnseenFilterSettingChanges() }
dumpManager.registerDumpable(this)
}
- private suspend fun trackUnseenNotificationsWhileUnlocked() {
- // Whether or not we're actively tracking unseen notifications to mark them as seen when
- // appropriate.
- val isTrackingUnseen: Flow<Boolean> =
- keyguardRepository.isKeyguardShowing
- // transformLatest so that we can cancel listening to keyguard transitions once
- // isKeyguardShowing changes (after a successful transition to the keyguard).
- .transformLatest { isShowing ->
- if (isShowing) {
- // If the keyguard is showing, we're not tracking unseen.
- emit(false)
- } else {
- // If the keyguard stops showing, then start tracking unseen notifications.
- emit(true)
- // If the screen is turning off, stop tracking, but if that transition is
- // cancelled, then start again.
- emitAll(
- keyguardTransitionRepository.transitions.map { step ->
- !step.isScreenTurningOff
- }
- )
- }
- }
- // Prevent double emit of `false` caused by transition to AOD, followed by keyguard
- // showing
+ private suspend fun trackSeenNotifications() {
+ // Whether or not keyguard is visible (or occluded).
+ val isKeyguardPresent: Flow<Boolean> =
+ keyguardTransitionRepository.transitions
+ .map { step -> step.to != KeyguardState.GONE }
.distinctUntilChanged()
.onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
- // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
- // showing again
- var clearUnseenOnBeginTracking = false
- isTrackingUnseen.collectLatest { trackingUnseen ->
- if (!trackingUnseen) {
- // Wait for the user to spend enough time on the lock screen before clearing unseen
- // set when unlocked
- awaitTimeSpentNotDozing(SEEN_TIMEOUT)
- clearUnseenOnBeginTracking = true
- logger.logSeenOnLockscreen()
+ // Separately track seen notifications while the device is locked, applying once the device
+ // is unlocked.
+ val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>()
+
+ // Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes.
+ isKeyguardPresent.collectLatest { isKeyguardPresent: Boolean ->
+ if (isKeyguardPresent) {
+ // Keyguard is not gone, notifications need to be visible for a certain threshold
+ // before being marked as seen
+ trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked)
} else {
- if (clearUnseenOnBeginTracking) {
- clearUnseenOnBeginTracking = false
- logger.logAllMarkedSeenOnUnlock()
- unseenNotifications.clear()
+ // Mark all seen-while-locked notifications as seen for real.
+ if (notificationsSeenWhileLocked.isNotEmpty()) {
+ unseenNotifications.removeAll(notificationsSeenWhileLocked)
+ logger.logAllMarkedSeenOnUnlock(
+ seenCount = notificationsSeenWhileLocked.size,
+ remainingUnseenCount = unseenNotifications.size
+ )
+ notificationsSeenWhileLocked.clear()
}
unseenNotifFilter.invalidateList("keyguard no longer showing")
- trackUnseenNotifications()
+ // Keyguard is gone, notifications can be immediately marked as seen when they
+ // become visible.
+ trackSeenNotificationsWhileUnlocked()
}
}
}
- private suspend fun awaitTimeSpentNotDozing(duration: Duration) {
- keyguardRepository.isDozing
- // Use transformLatest so that the timeout delay is cancelled if the device enters doze,
- // and is restarted when doze ends.
- .transformLatest { isDozing ->
- if (!isDozing) {
- delay(duration)
- // Signal timeout has completed
- emit(Unit)
+ /**
+ * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
+ * been "seen" while the device is on the keyguard.
+ */
+ private suspend fun trackSeenNotificationsWhileLocked(
+ notificationsSeenWhileLocked: MutableSet<NotificationEntry>,
+ ) = coroutineScope {
+ // Remove removed notifications from the set
+ launch {
+ unseenEntryRemoved.collect { entry ->
+ if (notificationsSeenWhileLocked.remove(entry)) {
+ logger.logRemoveSeenOnLockscreen(entry)
}
}
- // Suspend until the first emission
- .first()
+ }
+ // Use collectLatest so that the timeout delay is cancelled if the device enters doze, and
+ // is restarted when doze ends.
+ keyguardRepository.isDozing.collectLatest { isDozing ->
+ if (!isDozing) {
+ trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked)
+ }
+ }
}
- // Track "unseen" notifications, marking them as seen when either shade is expanded or the
+ /**
+ * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
+ * been "seen" while the device is on the keyguard and not dozing. Any new and existing unseen
+ * notifications are not marked as seen until they are visible for the [SEEN_TIMEOUT] duration.
+ */
+ private suspend fun trackSeenNotificationsWhileLockedAndNotDozing(
+ notificationsSeenWhileLocked: MutableSet<NotificationEntry>
+ ) = coroutineScope {
+ // All child tracking jobs will be cancelled automatically when this is cancelled.
+ val trackingJobsByEntry = mutableMapOf<NotificationEntry, Job>()
+
+ /**
+ * Wait for the user to spend enough time on the lock screen before removing notification
+ * from unseen set upon unlock.
+ */
+ suspend fun trackSeenDurationThreshold(entry: NotificationEntry) {
+ if (notificationsSeenWhileLocked.remove(entry)) {
+ logger.logResetSeenOnLockscreen(entry)
+ }
+ delay(SEEN_TIMEOUT)
+ notificationsSeenWhileLocked.add(entry)
+ trackingJobsByEntry.remove(entry)
+ logger.logSeenOnLockscreen(entry)
+ }
+
+ /** Stop any unseen tracking when a notification is removed. */
+ suspend fun stopTrackingRemovedNotifs(): Nothing =
+ unseenEntryRemoved.collect { entry ->
+ trackingJobsByEntry.remove(entry)?.let {
+ it.cancel()
+ logger.logStopTrackingLockscreenSeenDuration(entry)
+ }
+ }
+
+ /** Start tracking new notifications when they are posted. */
+ suspend fun trackNewUnseenNotifs(): Nothing = coroutineScope {
+ unseenEntryAdded.collect { entry ->
+ logger.logTrackingLockscreenSeenDuration(entry)
+ // If this is an update, reset the tracking.
+ trackingJobsByEntry[entry]?.let {
+ it.cancel()
+ logger.logResetSeenOnLockscreen(entry)
+ }
+ trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
+ }
+ }
+
+ // Start tracking for all notifications that are currently unseen.
+ logger.logTrackingLockscreenSeenDuration(unseenNotifications)
+ unseenNotifications.forEach { entry ->
+ trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
+ }
+
+ launch { trackNewUnseenNotifs() }
+ launch { stopTrackingRemovedNotifs() }
+ }
+
+ // Track "seen" notifications, marking them as such when either shade is expanded or the
// notification becomes heads up.
- private suspend fun trackUnseenNotifications() {
+ private suspend fun trackSeenNotificationsWhileUnlocked() {
coroutineScope {
launch { clearUnseenNotificationsWhenShadeIsExpanded() }
launch { markHeadsUpNotificationsAsSeen() }
@@ -212,7 +260,7 @@
}
}
- private suspend fun invalidateWhenUnseenSettingChanges() {
+ private suspend fun trackUnseenFilterSettingChanges() {
secureSettings
// emit whenever the setting has changed
.observerFlow(
@@ -228,17 +276,23 @@
UserHandle.USER_CURRENT,
) == 1
}
+ // don't emit anything if nothing has changed
+ .distinctUntilChanged()
// perform lookups on the bg thread pool
.flowOn(bgDispatcher)
// only track the most recent emission, if events are happening faster than they can be
// consumed
.conflate()
- // update local field and invalidate if necessary
- .collect { setting ->
+ .collectLatest { setting ->
+ // update local field and invalidate if necessary
if (setting != unseenFilterEnabled) {
unseenFilterEnabled = setting
unseenNotifFilter.invalidateList("unseen setting changed")
}
+ // if the setting is enabled, then start tracking and filtering unseen notifications
+ if (setting) {
+ trackSeenNotifications()
+ }
}
}
@@ -250,6 +304,7 @@
) {
logger.logUnseenAdded(entry.key)
unseenNotifications.add(entry)
+ unseenEntryAdded.tryEmit(entry)
}
}
@@ -259,12 +314,14 @@
) {
logger.logUnseenUpdated(entry.key)
unseenNotifications.add(entry)
+ unseenEntryAdded.tryEmit(entry)
}
}
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
if (unseenNotifications.remove(entry)) {
logger.logUnseenRemoved(entry.key)
+ unseenEntryRemoved.tryEmit(entry)
}
}
}
@@ -347,6 +404,3 @@
private val SEEN_TIMEOUT = 5.seconds
}
}
-
-private val TransitionStep.isScreenTurningOff: Boolean
- get() = transitionState == TransitionState.STARTED && to != KeyguardState.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
index 4c33524..788659e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
@@ -19,6 +19,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.dagger.UnseenNotificationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import javax.inject.Inject
private const val TAG = "KeyguardCoordinator"
@@ -28,11 +29,14 @@
constructor(
@UnseenNotificationLog private val buffer: LogBuffer,
) {
- fun logSeenOnLockscreen() =
+ fun logSeenOnLockscreen(entry: NotificationEntry) =
buffer.log(
TAG,
LogLevel.DEBUG,
- "Notifications on lockscreen will be marked as seen when unlocked."
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = {
+ "Notification [$str1] on lockscreen will be marked as seen when unlocked."
+ },
)
fun logTrackingUnseen(trackingUnseen: Boolean) =
@@ -43,11 +47,21 @@
messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." },
)
- fun logAllMarkedSeenOnUnlock() =
+ fun logAllMarkedSeenOnUnlock(
+ seenCount: Int,
+ remainingUnseenCount: Int,
+ ) =
buffer.log(
TAG,
LogLevel.DEBUG,
- "Notifications have been marked as seen now that device is unlocked."
+ messageInitializer = {
+ int1 = seenCount
+ int2 = remainingUnseenCount
+ },
+ messagePrinter = {
+ "$int1 Notifications have been marked as seen now that device is unlocked. " +
+ "$int2 notifications remain unseen."
+ },
)
fun logShadeExpanded() =
@@ -96,4 +110,60 @@
messageInitializer = { str1 = key },
messagePrinter = { "Unseen notif has become heads up: $str1" },
)
+
+ fun logTrackingLockscreenSeenDuration(unseenNotifications: Set<NotificationEntry>) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = {
+ str1 = unseenNotifications.joinToString { it.key }
+ int1 = unseenNotifications.size
+ },
+ messagePrinter = {
+ "Tracking $int1 unseen notifications for lockscreen seen duration threshold: $str1"
+ },
+ )
+ }
+
+ fun logTrackingLockscreenSeenDuration(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = {
+ "Tracking new notification for lockscreen seen duration threshold: $str1"
+ },
+ )
+ }
+
+ fun logStopTrackingLockscreenSeenDuration(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = {
+ "Stop tracking removed notification for lockscreen seen duration threshold: $str1"
+ },
+ )
+ }
+
+ fun logResetSeenOnLockscreen(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = {
+ "Reset tracking updated notification for lockscreen seen duration threshold: $str1"
+ },
+ )
+ }
+
+ fun logRemoveSeenOnLockscreen(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = { "Notification marked as seen on lockscreen removed: $str1" },
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 8af488e..27510d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -359,9 +359,9 @@
}
@Override
- public long performRemoveAnimation(long duration, long delay,
- float translationDirection, boolean isHeadsUpAnimation, float endLocation,
- Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) {
+ public long performRemoveAnimation(long duration, long delay, float translationDirection,
+ boolean isHeadsUpAnimation, Runnable onFinishedRunnable,
+ AnimatorListenerAdapter animationListener) {
enableAppearDrawing(true);
mIsHeadsUpAnimation = isHeadsUpAnimation;
if (mDrawingAppearAnimation) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 30747db..b34c281 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2975,7 +2975,6 @@
long delay,
float translationDirection,
boolean isHeadsUpAnimation,
- float endLocation,
Runnable onFinishedRunnable,
AnimatorListenerAdapter animationListener) {
if (mMenuRow != null && mMenuRow.isMenuVisible()) {
@@ -2986,7 +2985,7 @@
public void onAnimationEnd(Animator animation) {
ExpandableNotificationRow.super.performRemoveAnimation(
duration, delay, translationDirection, isHeadsUpAnimation,
- endLocation, onFinishedRunnable, animationListener);
+ onFinishedRunnable, animationListener);
}
});
anim.start();
@@ -2994,7 +2993,7 @@
}
}
return super.performRemoveAnimation(duration, delay, translationDirection,
- isHeadsUpAnimation, endLocation, onFinishedRunnable, animationListener);
+ isHeadsUpAnimation, onFinishedRunnable, animationListener);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index f0e15c2..f986244 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -367,7 +367,6 @@
* such that the child appears to be going away to the top. 1
* Should mean the opposite.
* @param isHeadsUpAnimation Is this a headsUp animation.
- * @param endLocation The location where the horizonal heads up disappear animation should end.
* @param onFinishedRunnable A runnable which should be run when the animation is finished.
* @param animationListener An animation listener to add to the animation.
*
@@ -375,7 +374,7 @@
* animation starts.
*/
public abstract long performRemoveAnimation(long duration,
- long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation,
+ long delay, float translationDirection, boolean isHeadsUpAnimation,
Runnable onFinishedRunnable,
AnimatorListenerAdapter animationListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index b24cec1..0c686be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -235,7 +235,7 @@
@Override
public long performRemoveAnimation(long duration, long delay,
- float translationDirection, boolean isHeadsUpAnimation, float endLocation,
+ float translationDirection, boolean isHeadsUpAnimation,
Runnable onFinishedRunnable,
AnimatorListenerAdapter animationListener) {
// TODO: Use duration
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index b8f28b5..04308b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -69,11 +69,14 @@
canvas.clipPath(clipPath)
}
-
- override fun performRemoveAnimation(duration: Long, delay: Long, translationDirection: Float,
- isHeadsUpAnimation: Boolean, endLocation: Float,
- onFinishedRunnable: Runnable?,
- animationListener: AnimatorListenerAdapter?): Long {
+ override fun performRemoveAnimation(
+ duration: Long,
+ delay: Long,
+ translationDirection: Float,
+ isHeadsUpAnimation: Boolean,
+ onFinishedRunnable: Runnable?,
+ animationListener: AnimatorListenerAdapter?
+ ): Long {
return 0
}
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 7c66bbe5..ef7375a 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
@@ -424,7 +424,8 @@
}
};
- private final NotificationSwipeHelper.NotificationCallback mNotificationCallback =
+ @VisibleForTesting
+ final NotificationSwipeHelper.NotificationCallback mNotificationCallback =
new NotificationSwipeHelper.NotificationCallback() {
@Override
@@ -483,10 +484,11 @@
*/
public void handleChildViewDismissed(View view) {
+ // The View needs to clean up the Swipe states, e.g. roundness.
+ mView.onSwipeEnd();
if (mView.getClearAllInProgress()) {
return;
}
- mView.onSwipeEnd();
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
if (row.isHeadsUp()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index d73919b..2742a23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -27,8 +27,6 @@
import com.android.systemui.R;
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
@@ -427,7 +425,7 @@
}
changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
- 0, postAnimation, null);
+ postAnimation, null);
} else if (event.animationType ==
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
if (mHostLayout.isFullySwipedOut(changingView)) {
@@ -474,28 +472,12 @@
mTmpState.initFrom(changingView);
endRunnable = changingView::removeFromTransientContainer;
}
- float targetLocation = 0;
boolean needsAnimation = true;
if (changingView instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) changingView;
if (row.isDismissed()) {
needsAnimation = false;
}
-
- NotificationEntry entry = row.getEntry();
- StatusBarIconView icon = entry.getIcons().getStatusBarIcon();
- final StatusBarIconView centeredIcon = entry.getIcons().getCenteredIcon();
- if (centeredIcon != null && centeredIcon.getParent() != null) {
- icon = centeredIcon;
- }
- if (icon.getParent() != null) {
- icon.getLocationOnScreen(mTmpLocation);
- float iconPosition = mTmpLocation[0] - icon.getTranslationX()
- + ViewState.getFinalTranslationX(icon)
- + icon.getWidth() * 0.25f;
- mHostLayout.getLocationOnScreen(mTmpLocation);
- targetLocation = iconPosition - mTmpLocation[0];
- }
}
if (needsAnimation) {
@@ -515,7 +497,7 @@
}
long removeAnimationDelay = changingView.performRemoveAnimation(
ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
- 0, 0.0f, true /* isHeadsUpAppear */, targetLocation,
+ 0, 0.0f, true /* isHeadsUpAppear */,
postAnimation, getGlobalAnimationFinishedListener());
mAnimationProperties.delay += removeAnimationDelay;
} else if (endRunnable != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 8b5db28..8902a18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -42,6 +42,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.IWallpaperManager;
import android.app.KeyguardManager;
import android.app.Notification;
@@ -182,7 +183,6 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.shade.CameraLauncher;
-import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.QuickSettingsController;
@@ -477,14 +477,12 @@
private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy;
/** Controller for the Shade. */
- @VisibleForTesting
- ShadeSurface mShadeSurface;
+ private final ShadeSurface mShadeSurface;
private final ShadeLogger mShadeLogger;
// settings
private QSPanelController mQSPanelController;
- @VisibleForTesting
- QuickSettingsController mQsController;
+ private final QuickSettingsController mQsController;
KeyguardIndicationController mKeyguardIndicationController;
@@ -704,9 +702,11 @@
MetricsLogger metricsLogger,
ShadeLogger shadeLogger,
@UiBackground Executor uiBgExecutor,
+ ShadeSurface shadeSurface,
NotificationMediaManager notificationMediaManager,
NotificationLockscreenUserManager lockScreenUserManager,
NotificationRemoteInputManager remoteInputManager,
+ QuickSettingsController quickSettingsController,
UserSwitcherController userSwitcherController,
BatteryController batteryController,
SysuiColorExtractor colorExtractor,
@@ -805,9 +805,11 @@
mMetricsLogger = metricsLogger;
mShadeLogger = shadeLogger;
mUiBgExecutor = uiBgExecutor;
+ mShadeSurface = shadeSurface;
mMediaManager = notificationMediaManager;
mLockscreenUserManager = lockScreenUserManager;
mRemoteInputManager = remoteInputManager;
+ mQsController = quickSettingsController;
mUserSwitcherController = userSwitcherController;
mBatteryController = batteryController;
mColorExtractor = colorExtractor;
@@ -1611,13 +1613,9 @@
// (Right now, there's a circular dependency.)
mNotificationShadeWindowController.setWindowRootView(windowRootView);
mNotificationShadeWindowViewController.setupExpandedStatusBar();
- NotificationPanelViewController npvc =
- mCentralSurfacesComponent.getNotificationPanelViewController();
- mShadeSurface = npvc;
- mShadeController.setNotificationPanelViewController(npvc);
+ mShadeController.setShadeViewController(mShadeSurface);
mShadeController.setNotificationShadeWindowViewController(
mNotificationShadeWindowViewController);
- mQsController = mCentralSurfacesComponent.getQuickSettingsController();
mBackActionInteractor.setup(mQsController, mShadeSurface);
mPresenter = mCentralSurfacesComponent.getNotificationPresenter();
mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter();
@@ -1776,7 +1774,10 @@
EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
sbn.getKey());
wakeUpForFullScreenIntent();
- notification.fullScreenIntent.send();
+ ActivityOptions opts = ActivityOptions.makeBasic();
+ opts.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ notification.fullScreenIntent.send(opts.toBundle());
entry.notifyFullScreenIntentLaunched();
} catch (PendingIntent.CanceledException e) {
}
@@ -1813,7 +1814,7 @@
@Override
public void onStatusBarTrackpadEvent(MotionEvent event) {
- mCentralSurfacesComponent.getNotificationPanelViewController().handleExternalTouch(event);
+ mShadeSurface.handleExternalTouch(event);
}
private void onExpandedInvisible() {
@@ -2180,9 +2181,6 @@
*/
@Override
public void setLockscreenUser(int newUserId) {
- if (mLockscreenWallpaper != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
- mLockscreenWallpaper.setCurrentUser(newUserId);
- }
if (mWallpaperSupported) {
mWallpaperChangedReceiver.onReceive(mContext, null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index c07b5e0..b2c39f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -40,12 +40,17 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.CoreStartable;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.user.data.model.SelectedUserModel;
+import com.android.systemui.user.data.model.SelectionStatus;
+import com.android.systemui.user.data.repository.UserRepository;
+import com.android.systemui.util.kotlin.JavaAdapter;
import libcore.io.IoUtils;
@@ -59,7 +64,7 @@
*/
@SysUISingleton
public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
- Dumpable {
+ Dumpable, CoreStartable {
private static final String TAG = "LockscreenWallpaper";
@@ -72,6 +77,8 @@
private final WallpaperManager mWallpaperManager;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final Handler mH;
+ private final JavaAdapter mJavaAdapter;
+ private final UserRepository mUserRepository;
private boolean mCached;
private Bitmap mCache;
@@ -88,6 +95,8 @@
DumpManager dumpManager,
NotificationMediaManager mediaManager,
@Main Handler mainHandler,
+ JavaAdapter javaAdapter,
+ UserRepository userRepository,
UserTracker userTracker) {
dumpManager.registerDumpable(getClass().getSimpleName(), this);
mWallpaperManager = wallpaperManager;
@@ -95,6 +104,8 @@
mUpdateMonitor = keyguardUpdateMonitor;
mMediaManager = mediaManager;
mH = mainHandler;
+ mJavaAdapter = javaAdapter;
+ mUserRepository = userRepository;
if (iWallpaperManager != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
// Service is disabled on some devices like Automotive
@@ -106,6 +117,14 @@
}
}
+ @Override
+ public void start() {
+ if (!isLockscreenLiveWallpaperEnabled()) {
+ mJavaAdapter.alwaysCollectFlow(
+ mUserRepository.getSelectedUser(), this::setSelectedUser);
+ }
+ }
+
public Bitmap getBitmap() {
assertLockscreenLiveWallpaperNotEnabled();
@@ -169,9 +188,15 @@
}
}
- public void setCurrentUser(int user) {
+ private void setSelectedUser(SelectedUserModel selectedUserModel) {
assertLockscreenLiveWallpaperNotEnabled();
+ if (selectedUserModel.getSelectionStatus().equals(SelectionStatus.SELECTION_IN_PROGRESS)) {
+ // Wait until the selection has finished before updating.
+ return;
+ }
+
+ int user = selectedUserModel.getUserInfo().id;
if (user != mCurrentUserId) {
if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) {
mCached = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
index c618be8..4ae460a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
@@ -21,10 +21,8 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.android.systemui.scene.ui.view.WindowRootView;
-import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
-import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeHeaderController;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -89,14 +87,6 @@
NotificationShadeWindowViewController getNotificationShadeWindowViewController();
/**
- * Creates a NotificationPanelViewController.
- */
- NotificationPanelViewController getNotificationPanelViewController();
-
- /** Creates a QuickSettingsController. */
- QuickSettingsController getQuickSettingsController();
-
- /**
* Creates a StatusBarHeadsUpChangeListener.
*/
StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 421bdc6..ebdde78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -21,18 +21,15 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
-import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
@@ -44,10 +41,8 @@
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.settings.SecureSettings;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
-import dagger.multibindings.IntoSet;
import java.util.concurrent.Executor;
@@ -65,17 +60,6 @@
public static final String STATUS_BAR_FRAGMENT = "status_bar_fragment";
- /** */
- @Binds
- @CentralSurfacesComponent.CentralSurfacesScope
- abstract ShadeViewController bindsShadeViewController(
- NotificationPanelViewController notificationPanelViewController);
-
- @Binds
- @IntoSet
- abstract StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener(
- SystemBarAttributesListener systemBarAttributesListener);
-
/**
* Creates a new {@link CollapsedStatusBarFragment}.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 730ecde..97a1bd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -26,20 +26,23 @@
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
+import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.window.StatusBarWindowController;
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoSet;
+import dagger.multibindings.Multibinds;
+
import java.util.Optional;
import java.util.Set;
import javax.inject.Named;
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.Multibinds;
-
/** Dagger module for {@link StatusBarFragmentComponent}. */
@Module
public interface StatusBarFragmentModule {
@@ -151,4 +154,10 @@
/** */
@Multibinds
Set<StatusBarBoundsProvider.BoundsChangeListener> boundsChangeListeners();
+
+ /** */
+ @Binds
+ @IntoSet
+ StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener(
+ SystemBarAttributesListener systemBarAttributesListener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index a4fbc2c..a57be66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -96,7 +96,7 @@
networkId = DEMO_NET_ID,
isValidated = validated ?: true,
level = level ?: 0,
- ssid = ssid,
+ ssid = ssid ?: DEMO_NET_SSID,
// These fields below aren't supported in demo mode, since they aren't needed to satisfy
// the interface.
@@ -115,5 +115,6 @@
companion object {
private const val DEMO_NET_ID = 1234
+ private const val DEMO_NET_SSID = "Demo SSID"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index 22b4c9d..736b145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy
+import android.app.ActivityOptions
import android.app.Notification
import android.app.PendingIntent
import android.app.RemoteInput
@@ -275,7 +276,10 @@
entry.sbn.instanceId)
try {
- pendingIntent.send(view.context, 0, intent)
+ val options = ActivityOptions.makeBasic()
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ pendingIntent.send(view.context, 0, intent, null, null, null, options.toBundle())
} catch (e: PendingIntent.CanceledException) {
Log.i(TAG, "Unable to send remote input result", e)
uiEventLogger.logWithInstanceId(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index cac5e32..1776e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy
+import android.app.ActivityOptions
import android.app.Notification
import android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY
import android.app.PendingIntent
@@ -491,7 +492,11 @@
entry.setHasSentReply()
try {
val intent = createRemoteInputIntent(smartReplies, choice)
- smartReplies.pendingIntent.send(context, 0, intent)
+ val opts = ActivityOptions.makeBasic()
+ opts.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ smartReplies.pendingIntent.send(context, 0, intent, /* onFinished */null,
+ /* handler */ null, /* requiredPermission */ null, opts.toBundle())
} catch (e: PendingIntent.CanceledException) {
Log.w(TAG, "Unable to send smart reply", e)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index b135d0d..1c3a8850 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -28,6 +28,7 @@
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.Global;
@@ -122,7 +123,12 @@
userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
- updateZenModeConfig();
+ try {
+ Trace.beginSection("updateZenModeConfig");
+ updateZenModeConfig();
+ } finally {
+ Trace.endSection();
+ }
}
};
mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
index 3362097..fd7c30f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
@@ -103,6 +103,8 @@
if (mPendingIntent != null) {
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
+ options.setPendingIntentBackgroundActivityStartMode(
+ BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
mPendingIntent.send(options.toBundle());
}
} catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index bcf3b0c..4a9921e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -240,8 +240,10 @@
Insets.of(0, safeTouchRegionHeight, 0, 0));
}
lp.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()),
- new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()),
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars())
+ .setInsetsSize(Insets.of(0, height, 0, 0)),
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement())
+ .setInsetsSize(Insets.of(0, height, 0, 0)),
gestureInsetsProvider
};
return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt
new file mode 100644
index 0000000..cefd466
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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.user.data.model
+
+import android.content.pm.UserInfo
+
+/** A model for the currently selected user. */
+data class SelectedUserModel(
+ /** Information about the user. */
+ val userInfo: UserInfo,
+ /** The current status of the selection. */
+ val selectionStatus: SelectionStatus,
+)
+
+/** The current status of the selection. */
+enum class SelectionStatus {
+ /** This user has started being selected but the selection hasn't completed. */
+ SELECTION_IN_PROGRESS,
+ /** The selection of this user has completed. */
+ SELECTION_COMPLETE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 3de75ca..954765c 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -33,6 +33,8 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.settings.UserTracker
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -69,6 +71,9 @@
/** List of all users on the device. */
val userInfos: Flow<List<UserInfo>>
+ /** Information about the currently-selected user, including [UserInfo] and other details. */
+ val selectedUser: StateFlow<SelectedUserModel>
+
/** [UserInfo] of the currently-selected user. */
val selectedUserInfo: Flow<UserInfo>
@@ -146,9 +151,6 @@
private val _userInfos = MutableStateFlow<List<UserInfo>?>(null)
override val userInfos: Flow<List<UserInfo>> = _userInfos.filterNotNull()
- private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
- override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
-
override var mainUserId: Int = UserHandle.USER_NULL
private set
override var lastSelectedNonGuestUserId: Int = UserHandle.USER_NULL
@@ -174,12 +176,57 @@
override var isRefreshUsersPaused: Boolean = false
init {
- observeSelectedUser()
if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) {
observeUserSwitching()
}
}
+ override val selectedUser: StateFlow<SelectedUserModel> = run {
+ // Some callbacks don't modify the selection status, so maintain the current value.
+ var currentSelectionStatus = SelectionStatus.SELECTION_COMPLETE
+ conflatedCallbackFlow {
+ fun send(selectionStatus: SelectionStatus) {
+ currentSelectionStatus = selectionStatus
+ trySendWithFailureLogging(
+ SelectedUserModel(tracker.userInfo, selectionStatus),
+ TAG,
+ )
+ }
+
+ val callback =
+ object : UserTracker.Callback {
+ override fun onUserChanging(newUser: Int, userContext: Context) {
+ send(SelectionStatus.SELECTION_IN_PROGRESS)
+ }
+
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ send(SelectionStatus.SELECTION_COMPLETE)
+ }
+
+ override fun onProfilesChanged(profiles: List<UserInfo>) {
+ send(currentSelectionStatus)
+ }
+ }
+
+ tracker.addCallback(callback, mainDispatcher.asExecutor())
+ send(currentSelectionStatus)
+
+ awaitClose { tracker.removeCallback(callback) }
+ }
+ .onEach {
+ if (!it.userInfo.isGuest) {
+ lastSelectedNonGuestUserId = it.userInfo.id
+ }
+ }
+ .stateIn(
+ applicationScope,
+ SharingStarted.Eagerly,
+ initialValue = SelectedUserModel(tracker.userInfo, currentSelectionStatus)
+ )
+ }
+
+ override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
+
override fun refreshUsers() {
applicationScope.launch {
val result = withContext(backgroundDispatcher) { manager.aliveUsers }
@@ -201,7 +248,7 @@
}
override fun getSelectedUserInfo(): UserInfo {
- return checkNotNull(_selectedUserInfo.value)
+ return selectedUser.value.userInfo
}
override fun isSimpleUserSwitcher(): Boolean {
@@ -234,38 +281,6 @@
.launchIn(applicationScope)
}
- private fun observeSelectedUser() {
- conflatedCallbackFlow {
- fun send() {
- trySendWithFailureLogging(tracker.userInfo, TAG)
- }
-
- val callback =
- object : UserTracker.Callback {
- override fun onUserChanging(newUser: Int, userContext: Context) {
- send()
- }
-
- override fun onProfilesChanged(profiles: List<UserInfo>) {
- send()
- }
- }
-
- tracker.addCallback(callback, mainDispatcher.asExecutor())
- send()
-
- awaitClose { tracker.removeCallback(callback) }
- }
- .onEach {
- if (!it.isGuest) {
- lastSelectedNonGuestUserId = it.id
- }
-
- _selectedUserInfo.value = it
- }
- .launchIn(applicationScope)
- }
-
private suspend fun getSettings(): UserSwitcherSettingsModel {
return withContext(backgroundDispatcher) {
val isSimpleUserSwitcher =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 7456d34..9362220 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -476,7 +476,8 @@
mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
- mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 0dcd404..2318988 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -21,21 +21,22 @@
import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.ClockAnimations
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockEvents
-import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceConfig
+import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
import com.android.systemui.plugins.ClockTickRate
-import com.android.systemui.log.LogBuffer
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -64,7 +65,6 @@
import org.mockito.junit.MockitoJUnit
import java.util.TimeZone
import java.util.concurrent.Executor
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidTestingRunner::class)
@@ -122,7 +122,9 @@
bouncerRepository = bouncerRepository,
configurationRepository = FakeConfigurationRepository(),
),
- KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ ).keyguardTransitionInteractor,
broadcastDispatcher,
batteryController,
keyguardUpdateMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index b21cc6d..9e561ed 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -408,4 +408,18 @@
any(ClockRegistry.ClockChangeListener.class));
verify(mClockEventController, times).registerListeners(mView);
}
+
+ @Test
+ public void testSplitShadeEnabledSetToSmartspaceController() {
+ mController.setSplitShadeEnabled(true);
+ verify(mSmartspaceController, times(1)).setSplitShadeEnabled(true);
+ verify(mSmartspaceController, times(0)).setSplitShadeEnabled(false);
+ }
+
+ @Test
+ public void testSplitShadeDisabledSetToSmartspaceController() {
+ mController.setSplitShadeEnabled(false);
+ verify(mSmartspaceController, times(1)).setSplitShadeEnabled(false);
+ verify(mSmartspaceController, times(0)).setSplitShadeEnabled(true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 2eea9eb..5d75428 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -32,15 +32,18 @@
import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@@ -78,6 +81,9 @@
private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
private lateinit var fakeFeatureFlags: FakeFeatureFlags
+ @Captor
+ lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -107,7 +113,7 @@
}
@Test
- fun tabletopPostureIsDetectedFromStart() {
+ fun onViewAttached_deviceHalfFolded_propagatedToPatternView() {
overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
whenever(mPostureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
@@ -116,6 +122,26 @@
assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline())
}
+ @Test
+ fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() {
+ overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+ whenever(mPostureController.devicePosture)
+ .thenReturn(DEVICE_POSTURE_HALF_OPENED)
+
+ mKeyguardPatternViewController.onViewAttached()
+
+ // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
+ assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline())
+
+ // Simulate posture change to state DEVICE_POSTURE_OPENED with callback
+ verify(mPostureController).addCallback(postureCallbackCaptor.capture())
+ val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value
+ postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ // Verify view is now in posture state DEVICE_POSTURE_OPENED
+ assertThat(getPatternTopGuideline()).isNotEqualTo(getExpectedTopGuideline())
+ }
+
private fun getPatternTopGuideline(): Float {
val cs = ConstraintSet()
val container =
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 5a56baf..9db267c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -30,12 +30,16 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.any
@@ -79,7 +83,9 @@
@Mock lateinit var deleteButton: NumPadButton
@Mock lateinit var enterButton: View
- lateinit var pinViewController: KeyguardPinViewController
+ private lateinit var pinViewController: KeyguardPinViewController
+
+ @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
@Before
fun setup() {
@@ -97,7 +103,51 @@
`when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
.thenReturn(deleteButton)
`when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
- constructViewController()
+ // For posture tests:
+ `when`(keyguardPinView.buttons).thenReturn(arrayOf())
+
+ pinViewController =
+ KeyguardPinViewController(
+ keyguardPinView,
+ keyguardUpdateMonitor,
+ securityMode,
+ lockPatternUtils,
+ mKeyguardSecurityCallback,
+ keyguardMessageAreaControllerFactory,
+ mLatencyTracker,
+ liftToActivateListener,
+ mEmergencyButtonController,
+ falsingCollector,
+ postureController,
+ featureFlags
+ )
+ }
+
+ @Test
+ fun onViewAttached_deviceHalfFolded_propagatedToPinView() {
+ `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+
+ pinViewController.onViewAttached()
+
+ verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED)
+ }
+
+ @Test
+ fun onDevicePostureChanged_deviceHalfFolded_propagatedToPinView() {
+ `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+
+ // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
+ pinViewController.onViewAttached()
+
+ verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED)
+
+ // Simulate posture change to state DEVICE_POSTURE_OPENED with callback
+ verify(postureController).addCallback(postureCallbackCaptor.capture())
+ val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value
+ postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ // Verify view is now in posture state DEVICE_POSTURE_OPENED
+ verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_OPENED)
}
@Test
@@ -121,10 +171,8 @@
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
`when`(passwordTextView.text).thenReturn("")
- constructViewController()
pinViewController.startAppearAnimation()
-
verify(deleteButton).visibility = View.INVISIBLE
verify(enterButton).visibility = View.INVISIBLE
verify(passwordTextView).setUsePinShapes(true)
@@ -138,10 +186,8 @@
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
`when`(passwordTextView.text).thenReturn("")
- constructViewController()
pinViewController.startAppearAnimation()
-
verify(deleteButton).visibility = View.VISIBLE
verify(enterButton).visibility = View.VISIBLE
verify(passwordTextView).setUsePinShapes(true)
@@ -153,22 +199,4 @@
pinViewController.handleAttemptLockout(0)
verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt())
}
-
- fun constructViewController() {
- pinViewController =
- KeyguardPinViewController(
- keyguardPinView,
- keyguardUpdateMonitor,
- securityMode,
- lockPatternUtils,
- mKeyguardSecurityCallback,
- keyguardMessageAreaControllerFactory,
- mLatencyTracker,
- liftToActivateListener,
- mEmergencyButtonController,
- falsingCollector,
- postureController,
- featureFlags
- )
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index e561f1f..58b1edc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -62,12 +62,12 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.biometrics.SideFpsUiRequestSource;
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
@@ -395,6 +395,7 @@
// WHEN a request is made from the SimPin screens to show the next security method
when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
+ when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true);
mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
/* authenticated= */true,
TARGET_USER_ID,
@@ -423,6 +424,28 @@
}
@Test
+ public void showNextSecurityScreenOrFinish_SimPin_Swipe() {
+ // GIVEN the current security method is SimPin
+ when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
+ mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
+
+ // WHEN a request is made from the SimPin screens to show the next security method
+ when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
+ // WHEN security method is SWIPE
+ when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
+ mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
+ /* authenticated= */true,
+ TARGET_USER_ID,
+ /* bypassSecondaryLockScreen= */true,
+ SecurityMode.SimPin);
+
+ // THEN the next security method of None will dismiss keyguard.
+ verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt());
+ }
+
+
+ @Test
public void onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
getRegisteredSwipeListener();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index a2c6329..512e5dc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -155,4 +156,18 @@
verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 20f, true);
verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 20f, true);
}
+
+ @Test
+ public void splitShadeEnabledPassedToClockSwitchController() {
+ mController.setSplitShadeEnabled(true);
+ verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(true);
+ verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(false);
+ }
+
+ @Test
+ public void splitShadeDisabledPassedToClockSwitchController() {
+ mController.setSplitShadeEnabled(false);
+ verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(false);
+ verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index c88c4d6..75106e7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -19,6 +19,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
+import static com.android.systemui.flags.Flags.MIGRATE_LOCK_ICON;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
@@ -41,6 +42,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.doze.util.BurnInHelperKt;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -92,6 +94,7 @@
protected @Mock KeyguardTransitionRepository mTransitionRepository;
protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
protected FakeFeatureFlags mFeatureFlags;
+ protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
protected LockIconViewController mUnderTest;
@@ -143,6 +146,7 @@
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(FACE_AUTH_REFACTOR, false);
+ mFeatureFlags.set(MIGRATE_LOCK_ICON, false);
mUnderTest = new LockIconViewController(
mLockIconView,
mStatusBarStateController,
@@ -161,7 +165,8 @@
new KeyguardTransitionInteractor(mTransitionRepository,
TestScopeProvider.getTestScope().getBackgroundScope()),
KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
- mFeatureFlags
+ mFeatureFlags,
+ mPrimaryBouncerInteractor
);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index b6287598..ed6a891 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -33,6 +33,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Pair;
+import android.view.View;
import androidx.test.filters.SmallTest;
@@ -267,4 +268,75 @@
// THEN the lock icon is shown
verify(mLockIconView).setContentDescription(LOCKED_LABEL);
}
+
+ @Test
+ public void lockIconAccessibility_notVisibleToUser() {
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureKeyguardStateCallback();
+ captureKeyguardUpdateMonitorCallback();
+
+ // GIVEN user has unlocked with a biometric auth (ie: face auth)
+ // and biometric running state changes
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+ mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+ BiometricSourceType.FACE);
+ reset(mLockIconView);
+ when(mLockIconView.isVisibleToUser()).thenReturn(false);
+
+ // WHEN the unlocked state changes
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
+ mKeyguardStateCallback.onUnlockedChanged();
+
+ // THEN the lock icon is shown
+ verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+
+ @Test
+ public void lockIconAccessibility_bouncerAnimatingAway() {
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureKeyguardStateCallback();
+ captureKeyguardUpdateMonitorCallback();
+
+ // GIVEN user has unlocked with a biometric auth (ie: face auth)
+ // and biometric running state changes
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+ mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+ BiometricSourceType.FACE);
+ reset(mLockIconView);
+ when(mLockIconView.isVisibleToUser()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true);
+
+ // WHEN the unlocked state changes
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
+ mKeyguardStateCallback.onUnlockedChanged();
+
+ // THEN the lock icon is shown
+ verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+
+ @Test
+ public void lockIconAccessibility_bouncerNotAnimatingAway_viewVisible() {
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureKeyguardStateCallback();
+ captureKeyguardUpdateMonitorCallback();
+
+ // GIVEN user has unlocked with a biometric auth (ie: face auth)
+ // and biometric running state changes
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+ mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+ BiometricSourceType.FACE);
+ reset(mLockIconView);
+ when(mLockIconView.isVisibleToUser()).thenReturn(true);
+ when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(false);
+
+ // WHEN the unlocked state changes
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
+ mKeyguardStateCallback.onUnlockedChanged();
+
+ // THEN the lock icon is shown
+ verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index f64db78f..caf230d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -16,8 +16,10 @@
package com.android.systemui.accessibility;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
@@ -185,6 +187,19 @@
verify(mMagnificationSettingsController).closeMagnificationSettings();
}
+ @Test
+ public void onUserMagnificationScaleChanged() throws RemoteException {
+ final int testUserId = 1;
+ final float testScale = 3.0f;
+ mIWindowMagnificationConnection.onUserMagnificationScaleChanged(
+ testUserId, TEST_DISPLAY, testScale);
+ waitForIdleSync();
+
+ assertTrue(mWindowMagnification.mUsersScales.contains(testUserId));
+ assertEquals(mWindowMagnification.mUsersScales.get(testUserId).get(TEST_DISPLAY),
+ (Float) testScale);
+ }
+
private class FakeControllerSupplier extends
DisplayIdIndexSupplier<WindowMagnificationController> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
index a10f5dd..d5e6881 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.capture
@@ -67,7 +68,7 @@
@Mock private lateinit var userTracker: UserTracker
@Captor
- private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
+ private lateinit var seekBarChangeCaptor: ArgumentCaptor<OnSeekBarWithIconButtonsChangeListener>
@Before
fun setUp() {
@@ -176,16 +177,17 @@
fun progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOn() {
fontScalingDialog.show()
- val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
- fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
+ val iconStartFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_start_frame)!!
secureSettings.putIntForUser(
Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
OFF,
userTracker.userId
)
- // Default seekbar progress for font size is 1, set it to another progress 0
- seekBarWithIconButtonsView.setProgress(0)
+ // Default seekbar progress for font size is 1, click start icon to decrease the progress
+ iconStartFrame.performClick()
+ backgroundDelayableExecutor.runAllReady()
+ backgroundDelayableExecutor.advanceClockToNext()
backgroundDelayableExecutor.runAllReady()
val currentSettings =
@@ -208,7 +210,7 @@
)
.thenReturn(slider)
fontScalingDialog.show()
- verify(slider).setOnSeekBarChangeListener(capture(seekBarChangeCaptor))
+ verify(slider).setOnSeekBarWithIconButtonsChangeListener(capture(seekBarChangeCaptor))
val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
// Default seekbar progress for font size is 1, simulate dragging to 0 without
@@ -237,6 +239,15 @@
backgroundDelayableExecutor.advanceClockToNext()
backgroundDelayableExecutor.runAllReady()
+ // SeekBar interaction is finalized.
+ seekBarChangeCaptor.value.onUserInteractionFinalized(
+ seekBar,
+ OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER
+ )
+ backgroundDelayableExecutor.runAllReady()
+ backgroundDelayableExecutor.advanceClockToNext()
+ backgroundDelayableExecutor.runAllReady()
+
// Verify that the scale of font size has been updated.
systemScale =
systemSettings.getFloatForUser(
@@ -258,7 +269,7 @@
)
.thenReturn(slider)
fontScalingDialog.show()
- verify(slider).setOnSeekBarChangeListener(capture(seekBarChangeCaptor))
+ verify(slider).setOnSeekBarWithIconButtonsChangeListener(capture(seekBarChangeCaptor))
val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
// Default seekbar progress for font size is 1, simulate dragging to 0 without
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
new file mode 100644
index 0000000..7de78a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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.biometrics
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import org.junit.Assert.assertFalse
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class UdfpsBpViewControllerTest : SysuiTestCase() {
+
+ @JvmField @Rule var rule = MockitoJUnit.rule()
+
+ @Mock lateinit var udfpsBpView: UdfpsBpView
+ @Mock lateinit var statusBarStateController: StatusBarStateController
+ @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ @Mock lateinit var systemUIDialogManager: SystemUIDialogManager
+ @Mock lateinit var dumpManager: DumpManager
+
+ private lateinit var udfpsBpViewController: UdfpsBpViewController
+
+ @Before
+ fun setup() {
+ udfpsBpViewController =
+ UdfpsBpViewController(
+ udfpsBpView,
+ statusBarStateController,
+ shadeExpansionStateManager,
+ systemUIDialogManager,
+ dumpManager
+ )
+ }
+
+ @Test
+ fun testShouldNeverPauseAuth() {
+ assertFalse(udfpsBpViewController.shouldPauseAuth())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 2248755..0e0d0e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -43,11 +43,12 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -58,6 +59,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -70,6 +72,7 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+import javax.inject.Provider
import org.mockito.Mockito.`when` as whenever
private const val REQUEST_ID = 2L
@@ -80,6 +83,7 @@
private const val SENSOR_WIDTH = 30
private const val SENSOR_HEIGHT = 60
+@ExperimentalCoroutinesApi
@SmallTest
@RoboPilotTest
@RunWith(AndroidJUnit4::class)
@@ -116,6 +120,7 @@
@Mock private lateinit var udfpsUtils: UdfpsUtils
@Mock private lateinit var udfpsKeyguardAccessibilityDelegate:
UdfpsKeyguardAccessibilityDelegate
+ @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -148,6 +153,7 @@
controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils,
udfpsKeyguardAccessibilityDelegate,
+ udfpsKeyguardViewModels,
)
block()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 30e5447..58982d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -22,6 +22,7 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.internal.util.FunctionalUtils.ThrowingConsumer;
+import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -59,6 +60,7 @@
import android.os.RemoteException;
import android.os.VibrationAttributes;
import android.testing.TestableLooper.RunWithLooper;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
@@ -83,13 +85,14 @@
import com.android.systemui.biometrics.udfps.NormalizedTouchData;
import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
import com.android.systemui.biometrics.udfps.TouchProcessorResult;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -219,6 +222,8 @@
private SecureSettings mSecureSettings;
@Mock
private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
+ @Mock
+ private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
// Capture listeners so that they can be used to send events
@Captor
@@ -318,7 +323,7 @@
mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils,
mock(KeyguardFaceAuthInteractor.class),
- mUdfpsKeyguardAccessibilityDelegate);
+ mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -1200,8 +1205,53 @@
}
@Test
+ public void fingerDown_falsingManagerInformed() throws RemoteException {
+ final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
+ givenAcceptFingerDownEvent();
+
+ // WHEN ACTION_DOWN is received
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ touchProcessorResult.first);
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ mBiometricExecutor.runAllReady();
+ downEvent.recycle();
+
+ // THEN falsing manager is informed of the touch
+ verify(mFalsingManager).isFalseTouch(UDFPS_AUTHENTICATION);
+ }
+
+ @Test
public void onTouch_withNewTouchDetection_shouldCallNewFingerprintManagerPath()
throws RemoteException {
+ final Pair<TouchProcessorResult, TouchProcessorResult> processorResultDownAndUp =
+ givenAcceptFingerDownEvent();
+
+ // WHEN ACTION_DOWN is received
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultDownAndUp.first);
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ mBiometricExecutor.runAllReady();
+ downEvent.recycle();
+
+ // AND ACTION_UP is received
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultDownAndUp.second);
+ MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
+ mBiometricExecutor.runAllReady();
+ upEvent.recycle();
+
+ // THEN the new FingerprintManager path is invoked.
+ verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
+ anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
+ verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(),
+ anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
+ }
+
+ private Pair<TouchProcessorResult, TouchProcessorResult> givenAcceptFingerDownEvent()
+ throws RemoteException {
final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
0L);
final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch(
@@ -1227,27 +1277,7 @@
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
- // WHEN ACTION_DOWN is received
- when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
- processorResultDown);
- MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
- mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
- mBiometricExecutor.runAllReady();
- downEvent.recycle();
-
- // AND ACTION_UP is received
- when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
- processorResultUp);
- MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
- mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
- mBiometricExecutor.runAllReady();
- upEvent.recycle();
-
- // THEN the new FingerprintManager path is invoked.
- verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
- anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
- verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(),
- anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
+ return new Pair<>(processorResultDown, processorResultUp);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index 263ce1a..8f8004f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -7,7 +7,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -54,10 +54,11 @@
LogContextInteractorImpl(
testScope.backgroundScope,
foldProvider,
- KeyguardTransitionInteractor(
- keyguardTransitionRepository,
- testScope.backgroundScope
- ),
+ KeyguardTransitionInteractorFactory.create(
+ repository = keyguardTransitionRepository,
+ scope = testScope.backgroundScope,
+ )
+ .keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 22ac1b6..f811ce0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -42,6 +42,7 @@
)
private val underTest =
PinBouncerViewModel(
+ applicationContext = context,
applicationScope = testScope.backgroundScope,
interactor =
utils.bouncerInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 61432e2..608a187 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -67,6 +67,7 @@
)
private val underTest =
PinBouncerViewModel(
+ applicationContext = context,
applicationScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true).asStateFlow(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
index afd9be5..f0006e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
@@ -18,6 +18,13 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.ViewGroup;
@@ -28,10 +35,15 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
/**
* Tests for {@link SeekBarWithIconButtonsView}
@@ -48,14 +60,22 @@
private SeekBar mSeekbar;
private SeekBarWithIconButtonsView mIconDiscreteSliderLinearLayout;
+ @Mock
+ private OnSeekBarWithIconButtonsChangeListener mOnSeekBarChangeListener;
+
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
mIconDiscreteSliderLinearLayout = new SeekBarWithIconButtonsView(mContext);
mIconStart = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start);
mIconEnd = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end);
mIconStartFrame = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start_frame);
mIconEndFrame = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end_frame);
mSeekbar = mIconDiscreteSliderLinearLayout.findViewById(R.id.seekbar);
+
+ mIconDiscreteSliderLinearLayout.setOnSeekBarWithIconButtonsChangeListener(
+ mOnSeekBarChangeListener);
}
@Test
@@ -109,6 +129,49 @@
}
@Test
+ public void setProgress_onlyOnProgressChangedTriggeredWithFromUserFalse() {
+ reset(mOnSeekBarChangeListener);
+ mIconDiscreteSliderLinearLayout.setProgress(1);
+
+ verify(mOnSeekBarChangeListener).onProgressChanged(
+ eq(mSeekbar), /* progress= */ eq(1), /* fromUser= */ eq(false));
+ verify(mOnSeekBarChangeListener, never()).onStartTrackingTouch(/* seekBar= */ any());
+ verify(mOnSeekBarChangeListener, never()).onStopTrackingTouch(/* seekBar= */ any());
+ verify(mOnSeekBarChangeListener, never()).onUserInteractionFinalized(
+ /* seekBar= */any(), /* control= */ anyInt());
+ }
+
+ @Test
+ public void clickIconEnd_triggerCallbacksInSequence() {
+ final int magnitude = mIconDiscreteSliderLinearLayout.getChangeMagnitude();
+ mIconDiscreteSliderLinearLayout.setProgress(0);
+ reset(mOnSeekBarChangeListener);
+
+ mIconEndFrame.performClick();
+
+ InOrder inOrder = Mockito.inOrder(mOnSeekBarChangeListener);
+ inOrder.verify(mOnSeekBarChangeListener).onProgressChanged(
+ eq(mSeekbar), /* progress= */ eq(magnitude), /* fromUser= */ eq(true));
+ inOrder.verify(mOnSeekBarChangeListener).onUserInteractionFinalized(
+ eq(mSeekbar), eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON));
+ }
+
+ @Test
+ public void clickIconStart_triggerCallbacksInSequence() {
+ final int magnitude = mIconDiscreteSliderLinearLayout.getChangeMagnitude();
+ mIconDiscreteSliderLinearLayout.setProgress(magnitude);
+ reset(mOnSeekBarChangeListener);
+
+ mIconStartFrame.performClick();
+
+ InOrder inOrder = Mockito.inOrder(mOnSeekBarChangeListener);
+ inOrder.verify(mOnSeekBarChangeListener).onProgressChanged(
+ eq(mSeekbar), /* progress= */ eq(0), /* fromUser= */ eq(true));
+ inOrder.verify(mOnSeekBarChangeListener).onUserInteractionFinalized(
+ eq(mSeekbar), eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON));
+ }
+
+ @Test
public void setProgressStateLabels_getExpectedStateDescriptionOnInitialization() {
String[] stateLabels = new String[]{"1", "2", "3", "4", "5"};
mIconDiscreteSliderLinearLayout.setMax(stateLabels.length);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
index 4210675..71d2ec1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -11,9 +11,9 @@
import android.window.OnBackInvokedDispatcher
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.flags.FakeFeatureFlags
@@ -63,24 +63,19 @@
@JvmField
var activityRule =
ActivityTestRule(
- object :
- SingleActivityFactory<TestableControlsEditingActivity>(
- TestableControlsEditingActivity::class.java
- ) {
- override fun create(intent: Intent?): TestableControlsEditingActivity {
- return TestableControlsEditingActivity(
- featureFlags,
- uiExecutor,
- controller,
- userTracker,
- customIconCache,
- mockDispatcher,
- latch
- )
- }
+ /* activityFactory= */ SingleActivityFactory {
+ TestableControlsEditingActivity(
+ featureFlags,
+ uiExecutor,
+ controller,
+ userTracker,
+ customIconCache,
+ mockDispatcher,
+ latch
+ )
},
- false,
- false
+ /* initialTouchMode= */ false,
+ /* launchActivity= */ false,
)
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index f4cc8bc..f11c296 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -13,9 +13,9 @@
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
@@ -91,24 +91,19 @@
@JvmField
var activityRule =
ActivityTestRule(
- object :
- SingleActivityFactory<TestableControlsFavoritingActivity>(
- TestableControlsFavoritingActivity::class.java
- ) {
- override fun create(intent: Intent?): TestableControlsFavoritingActivity {
- return TestableControlsFavoritingActivity(
- featureFlags,
- executor,
- controller,
- listingController,
- userTracker,
- mockDispatcher,
- latch
- )
- }
+ /* activityFactory= */ SingleActivityFactory {
+ TestableControlsFavoritingActivity(
+ featureFlags,
+ executor,
+ controller,
+ listingController,
+ userTracker,
+ mockDispatcher,
+ latch
+ )
},
- false,
- false
+ /* initialTouchMode= */ false,
+ /* launchActivity= */ false,
)
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
index 4ba6718..d17495f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
@@ -29,8 +29,8 @@
import android.window.OnBackInvokedDispatcher
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
@@ -91,26 +91,21 @@
@JvmField
var activityRule =
ActivityTestRule(
- object :
- SingleActivityFactory<TestableControlsProviderSelectorActivity>(
- TestableControlsProviderSelectorActivity::class.java
- ) {
- override fun create(intent: Intent?): TestableControlsProviderSelectorActivity {
- return TestableControlsProviderSelectorActivity(
- executor,
- backExecutor,
- listingController,
- controlsController,
- userTracker,
- authorizedPanelsRepository,
- dialogFactory,
- mockDispatcher,
- latch
- )
- }
+ /* activityFactory= */ SingleActivityFactory {
+ TestableControlsProviderSelectorActivity(
+ executor,
+ backExecutor,
+ listingController,
+ controlsController,
+ userTracker,
+ authorizedPanelsRepository,
+ dialogFactory,
+ mockDispatcher,
+ latch
+ )
},
- false,
- false
+ /* initialTouchMode= */ false,
+ /* launchActivity= */ false,
)
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
index 314b176..ca970bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
@@ -30,8 +30,8 @@
import androidx.lifecycle.Lifecycle
import androidx.test.filters.MediumTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.settings.UserTracker
@@ -81,19 +81,18 @@
@Rule
@JvmField
- var activityRule = ActivityTestRule<TestControlsRequestDialog>(
- object : SingleActivityFactory<TestControlsRequestDialog>(
- TestControlsRequestDialog::class.java
- ) {
- override fun create(intent: Intent?): TestControlsRequestDialog {
- return TestControlsRequestDialog(
- mainExecutor,
- controller,
- userTracker,
- listingController
- )
- }
- }, false, false)
+ var activityRule = ActivityTestRule(
+ /* activityFactory= */ SingleActivityFactory {
+ TestControlsRequestDialog(
+ mainExecutor,
+ controller,
+ userTracker,
+ listingController
+ )
+ },
+ /* initialTouchMode= */ false,
+ /* launchActivity= */ false,
+ )
private lateinit var control: Control
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
index 2d3e10e..e279d28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
@@ -23,8 +23,8 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.flags.FeatureFlags
@@ -53,23 +53,18 @@
@JvmField
var activityRule =
ActivityTestRule(
- object :
- SingleActivityFactory<TestableControlsActivity>(
- TestableControlsActivity::class.java
- ) {
- override fun create(intent: Intent?): TestableControlsActivity {
- return TestableControlsActivity(
- uiController,
- broadcastDispatcher,
- dreamManager,
- featureFlags,
- controlsSettingsDialogManager,
- keyguardStateController,
- )
- }
+ /* activityFactory= */ SingleActivityFactory {
+ TestableControlsActivity(
+ uiController,
+ broadcastDispatcher,
+ dreamManager,
+ featureFlags,
+ controlsSettingsDialogManager,
+ keyguardStateController,
+ )
},
- false,
- false
+ /* initialTouchMode= */ false,
+ /* launchActivity= */ false,
)
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 729a1cc..ce8028c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -19,6 +19,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -31,6 +32,7 @@
import android.media.MediaMetadata;
import android.media.session.PlaybackState;
import android.net.Uri;
+import android.os.Handler;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -166,6 +168,7 @@
@Test
public void updatesClock() {
+ clearInvocations(mContentResolver);
mProvider.mKeyguardUpdateMonitorCallback.onTimeChanged();
TestableLooper.get(this).processAllMessages();
verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
@@ -217,11 +220,13 @@
reset(mContentResolver);
mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
PlaybackState.STATE_PLAYING);
+ TestableLooper.get(this).processAllMessages();
verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
// Hides after waking up
reset(mContentResolver);
mProvider.onDozingChanged(false);
+ TestableLooper.get(this).processAllMessages();
verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
}
@@ -231,6 +236,7 @@
mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
PlaybackState.STATE_PLAYING);
reset(mContentResolver);
+ TestableLooper.get(this).processAllMessages();
// Show media when dozing
mProvider.onDozingChanged(true);
verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
@@ -272,6 +278,8 @@
mMediaManager = KeyguardSliceProviderTest.this.mNotificationMediaManager;
mKeyguardUpdateMonitor = KeyguardSliceProviderTest.this.mKeyguardUpdateMonitor;
mUserTracker = KeyguardSliceProviderTest.this.mUserTracker;
+ mBgHandler =
+ new Handler(TestableLooper.get(KeyguardSliceProviderTest.this).getLooper());
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 6f7c217..666978e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -205,6 +205,8 @@
when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot);
when(mDreamingToLockscreenTransitionViewModel.getDreamOverlayAlpha())
.thenReturn(mock(Flow.class));
+ when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded())
+ .thenReturn(mock(Flow.class));
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
mConfigurationController, mViewMediator, mKeyguardBypassController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index b4bd473..925ac30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -10,7 +10,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -67,10 +67,11 @@
resourceTrimmer =
ResourceTrimmer(
keyguardInteractor,
- KeyguardTransitionInteractor(
- keyguardTransitionRepository,
- testScope.backgroundScope
- ),
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = keyguardTransitionRepository,
+ )
+ .keyguardTransitionInteractor,
globalWindowManager,
testScope.backgroundScope,
testDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index e042564..d62db5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -49,7 +49,7 @@
import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.AuthenticationStatus
import com.android.systemui.keyguard.shared.model.DetectionStatus
import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
@@ -216,7 +216,11 @@
)
keyguardTransitionRepository = FakeKeyguardTransitionRepository()
val keyguardTransitionInteractor =
- KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = keyguardTransitionRepository,
+ )
+ .keyguardTransitionInteractor
return DeviceEntryFaceAuthRepositoryImpl(
mContext,
fmOverride,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index ee5c1cc3..3e81cd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -89,7 +89,11 @@
faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
keyguardTransitionRepository = FakeKeyguardTransitionRepository()
keyguardTransitionInteractor =
- KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = keyguardTransitionRepository,
+ )
+ .keyguardTransitionInteractor
underTest =
SystemUIKeyguardFaceAuthInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index e9c22f9..0050d64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -292,10 +292,11 @@
appContext = mContext,
scope = testScope.backgroundScope,
transitionInteractor =
- KeyguardTransitionInteractor(
- keyguardTransitionRepository,
- testScope.backgroundScope
- ),
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = keyguardTransitionRepository,
+ )
+ .keyguardTransitionInteractor,
repository = keyguardRepository,
logger = logger,
featureFlags =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index fa4941c..9e9c25e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -17,9 +17,9 @@
package com.android.systemui.keyguard.domain.interactor
-import com.android.systemui.RoboPilotTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -54,7 +54,10 @@
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
- underTest = KeyguardTransitionInteractor(repository, testScope.backgroundScope)
+ underTest = KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = repository,
+ ).keyguardTransitionInteractor
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index b559015..d01a46e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -19,6 +19,7 @@
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.keyguard.TestScopeProvider
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.flags.FakeFeatureFlags
@@ -92,76 +93,97 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- testScope = TestScope()
+ testScope = TestScopeProvider.getTestScope()
keyguardRepository = FakeKeyguardRepository()
bouncerRepository = FakeKeyguardBouncerRepository()
shadeRepository = FakeShadeRepository()
transitionRepository = spy(FakeKeyguardTransitionRepository())
- transitionInteractor = KeyguardTransitionInteractor(
- transitionRepository, testScope.backgroundScope)
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
- fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor(
- scope = testScope,
- keyguardInteractor = createKeyguardInteractor(),
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- shadeRepository = shadeRepository,
- ).apply { start() }
+ transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope,
+ repository = transitionRepository,
+ )
+ .keyguardTransitionInteractor
- fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor(
- scope = testScope,
- keyguardInteractor = createKeyguardInteractor(),
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- keyguardSecurityModel = keyguardSecurityModel,
- ).apply { start() }
+ fromLockscreenTransitionInteractor =
+ FromLockscreenTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = createKeyguardInteractor(),
+ transitionRepository = transitionRepository,
+ transitionInteractor = transitionInteractor,
+ shadeRepository = shadeRepository,
+ )
+ .apply { start() }
- fromDreamingTransitionInteractor = FromDreamingTransitionInteractor(
- scope = testScope,
- keyguardInteractor = createKeyguardInteractor(),
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- ).apply { start() }
+ fromPrimaryBouncerTransitionInteractor =
+ FromPrimaryBouncerTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = createKeyguardInteractor(),
+ transitionRepository = transitionRepository,
+ transitionInteractor = transitionInteractor,
+ keyguardSecurityModel = keyguardSecurityModel,
+ )
+ .apply { start() }
- fromAodTransitionInteractor = FromAodTransitionInteractor(
- scope = testScope,
- keyguardInteractor = createKeyguardInteractor(),
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- ).apply { start() }
+ fromDreamingTransitionInteractor =
+ FromDreamingTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = createKeyguardInteractor(),
+ transitionRepository = transitionRepository,
+ transitionInteractor = transitionInteractor,
+ )
+ .apply { start() }
- fromGoneTransitionInteractor = FromGoneTransitionInteractor(
- scope = testScope,
- keyguardInteractor = createKeyguardInteractor(),
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- ).apply { start() }
+ fromAodTransitionInteractor =
+ FromAodTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = createKeyguardInteractor(),
+ transitionRepository = transitionRepository,
+ transitionInteractor = transitionInteractor,
+ )
+ .apply { start() }
- fromDozingTransitionInteractor = FromDozingTransitionInteractor(
- scope = testScope,
- keyguardInteractor = createKeyguardInteractor(),
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- ).apply { start() }
+ fromGoneTransitionInteractor =
+ FromGoneTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = createKeyguardInteractor(),
+ transitionRepository = transitionRepository,
+ transitionInteractor = transitionInteractor,
+ )
+ .apply { start() }
- fromOccludedTransitionInteractor = FromOccludedTransitionInteractor(
- scope = testScope,
- keyguardInteractor = createKeyguardInteractor(),
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- ).apply { start() }
+ fromDozingTransitionInteractor =
+ FromDozingTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = createKeyguardInteractor(),
+ transitionRepository = transitionRepository,
+ transitionInteractor = transitionInteractor,
+ )
+ .apply { start() }
- fromAlternateBouncerTransitionInteractor = FromAlternateBouncerTransitionInteractor(
- scope = testScope,
- keyguardInteractor = createKeyguardInteractor(),
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- ).apply { start() }
+ fromOccludedTransitionInteractor =
+ FromOccludedTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = createKeyguardInteractor(),
+ transitionRepository = transitionRepository,
+ transitionInteractor = transitionInteractor,
+ )
+ .apply { start() }
+
+ fromAlternateBouncerTransitionInteractor =
+ FromAlternateBouncerTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = createKeyguardInteractor(),
+ transitionRepository = transitionRepository,
+ transitionInteractor = transitionInteractor,
+ )
+ .apply { start() }
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 08e99dc..6e7ba6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -46,7 +46,11 @@
private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
private val keyguardTransitionInteractor =
- KeyguardTransitionInteractor(fakeKeyguardTransitionRepository, TestScope().backgroundScope)
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = fakeKeyguardTransitionRepository,
+ )
+ .keyguardTransitionInteractor
private lateinit var underTest: LightRevealScrimInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
new file mode 100644
index 0000000..1baca21
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 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.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsKeyguardInteractorTest : SysuiTestCase() {
+ private val burnInProgress = 1f
+ private val burnInYOffset = 20
+ private val burnInXOffset = 10
+
+ private lateinit var testScope: TestScope
+ private lateinit var configRepository: FakeConfigurationRepository
+ private lateinit var bouncerRepository: KeyguardBouncerRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var fakeCommandQueue: FakeCommandQueue
+ private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var burnInInteractor: BurnInInteractor
+
+ @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+
+ private lateinit var underTest: UdfpsKeyguardInteractor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ testScope = TestScope()
+ configRepository = FakeConfigurationRepository()
+ keyguardRepository = FakeKeyguardRepository()
+ bouncerRepository = FakeKeyguardBouncerRepository()
+ fakeCommandQueue = FakeCommandQueue()
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+ set(Flags.FACE_AUTH_REFACTOR, false)
+ }
+ burnInInteractor =
+ BurnInInteractor(
+ context,
+ burnInHelper,
+ testScope.backgroundScope,
+ configRepository,
+ FakeSystemClock(),
+ )
+
+ underTest =
+ UdfpsKeyguardInteractor(
+ configRepository,
+ burnInInteractor,
+ KeyguardInteractor(
+ keyguardRepository,
+ fakeCommandQueue,
+ featureFlags,
+ bouncerRepository,
+ configRepository,
+ ),
+ )
+ }
+
+ @Test
+ fun dozeChanges_updatesUdfpsAodModel() =
+ testScope.runTest {
+ val burnInOffsets by collectLastValue(underTest.burnInOffsets)
+ initializeBurnInOffsets()
+
+ // WHEN we're not dozing
+ setAwake()
+ runCurrent()
+
+ // THEN burn in offsets are 0
+ assertThat(burnInOffsets?.burnInProgress).isEqualTo(0f)
+ assertThat(burnInOffsets?.burnInYOffset).isEqualTo(0)
+ assertThat(burnInOffsets?.burnInXOffset).isEqualTo(0)
+
+ // WHEN we're in the middle of the doze amount change
+ keyguardRepository.setDozeAmount(.50f)
+ runCurrent()
+
+ // THEN burn in is updated (between 0 and the full offset)
+ assertThat(burnInOffsets?.burnInProgress).isGreaterThan(0f)
+ assertThat(burnInOffsets?.burnInYOffset).isGreaterThan(0)
+ assertThat(burnInOffsets?.burnInXOffset).isGreaterThan(0)
+ assertThat(burnInOffsets?.burnInProgress).isLessThan(burnInProgress)
+ assertThat(burnInOffsets?.burnInYOffset).isLessThan(burnInYOffset)
+ assertThat(burnInOffsets?.burnInXOffset).isLessThan(burnInXOffset)
+
+ // WHEN we're fully dozing
+ keyguardRepository.setDozeAmount(1f)
+ runCurrent()
+
+ // THEN burn in offsets are updated to final current values (for the given time)
+ assertThat(burnInOffsets?.burnInProgress).isEqualTo(burnInProgress)
+ assertThat(burnInOffsets?.burnInYOffset).isEqualTo(burnInYOffset)
+ assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset)
+ }
+
+ private fun initializeBurnInOffsets() {
+ whenever(burnInHelper.burnInProgressOffset()).thenReturn(burnInProgress)
+ whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(true)))
+ .thenReturn(burnInXOffset)
+ whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(false)))
+ .thenReturn(burnInYOffset)
+ }
+
+ private fun setAwake() {
+ keyguardRepository.setDozeAmount(0f)
+ burnInInteractor.dozeTimeTick()
+
+ bouncerRepository.setAlternateVisible(false)
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ bouncerRepository.setPrimaryShow(false)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.AWAKE,
+ WakeSleepReason.POWER_BUTTON,
+ WakeSleepReason.POWER_BUTTON,
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt
new file mode 100644
index 0000000..2e97208
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.view.layout
+
+import android.graphics.Point
+import android.view.ViewGroup
+import android.view.WindowManager
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.monet.utils.ArgbSubject.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class DefaultLockscreenLayoutTest : SysuiTestCase() {
+ private lateinit var defaultLockscreenLayout: DefaultLockscreenLayout
+ private lateinit var rootView: KeyguardRootView
+ @Mock private lateinit var authController: AuthController
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ rootView = KeyguardRootView(context, null)
+ defaultLockscreenLayout =
+ DefaultLockscreenLayout(authController, keyguardUpdateMonitor, windowManager, context)
+ }
+
+ @Test
+ fun testLayoutViews_KeyguardIndicationArea() {
+ defaultLockscreenLayout.layoutViews(rootView)
+ val constraint = getViewConstraint(R.id.keyguard_indication_area)
+ assertThat(constraint.layout.bottomToBottom).isEqualTo(ConstraintSet.PARENT_ID)
+ assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
+ assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID)
+ assertThat(constraint.layout.mWidth).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT)
+ assertThat(constraint.layout.mHeight).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT)
+ }
+
+ @Test
+ fun testLayoutViews_lockIconView() {
+ defaultLockscreenLayout.layoutViews(rootView)
+ val constraint = getViewConstraint(R.id.lock_icon_view)
+ assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
+ assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
+ }
+
+ @Test
+ fun testCenterLockIcon() {
+ defaultLockscreenLayout.centerLockIcon(Point(5, 6), 1F, 5, rootView)
+ val constraint = getViewConstraint(R.id.lock_icon_view)
+
+ assertThat(constraint.layout.mWidth).isEqualTo(2)
+ assertThat(constraint.layout.mHeight).isEqualTo(2)
+ assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
+ assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
+ assertThat(constraint.layout.topMargin).isEqualTo(5)
+ assertThat(constraint.layout.startMargin).isEqualTo(4)
+ }
+
+ /** Get the ConstraintLayout constraint of the view. */
+ private fun getViewConstraint(viewId: Int): ConstraintSet.Constraint {
+ val constraintSet = ConstraintSet()
+ constraintSet.clone(rootView)
+ return constraintSet.getConstraint(viewId)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt
new file mode 100644
index 0000000..145b2fd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.view.layout
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import java.io.PrintWriter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class KeyguardLayoutManagerCommandListenerTest : SysuiTestCase() {
+ private lateinit var keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener
+ @Mock private lateinit var commandRegistry: CommandRegistry
+ @Mock private lateinit var keyguardLayoutManager: KeyguardLayoutManager
+ @Mock private lateinit var pw: PrintWriter
+ private lateinit var command: () -> Command
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ keyguardLayoutManagerCommandListener =
+ KeyguardLayoutManagerCommandListener(
+ commandRegistry,
+ keyguardLayoutManager,
+ )
+ keyguardLayoutManagerCommandListener.start()
+ command =
+ withArgCaptor<() -> Command> {
+ verify(commandRegistry).registerCommand(eq("layout"), capture())
+ }
+ }
+
+ @Test
+ fun testHelp() {
+ command().execute(pw, listOf("help"))
+ verify(pw, atLeastOnce()).println(anyString())
+ verify(keyguardLayoutManager, never()).transitionToLayout(anyString())
+ }
+
+ @Test
+ fun testBlank() {
+ command().execute(pw, listOf())
+ verify(pw, atLeastOnce()).println(anyString())
+ verify(keyguardLayoutManager, never()).transitionToLayout(anyString())
+ }
+
+ @Test
+ fun testValidArg() {
+ bindFakeIdMapToLayoutManager()
+ command().execute(pw, listOf("fake"))
+ verify(keyguardLayoutManager).transitionToLayout("fake")
+ }
+
+ private fun bindFakeIdMapToLayoutManager() {
+ val map = mapOf("fake" to mock(LockscreenLayout::class.java))
+ whenever(keyguardLayoutManager.layoutIdMap).thenReturn(map)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt
new file mode 100644
index 0000000..95b2030
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.view.layout
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class KeyguardLayoutManagerTest : SysuiTestCase() {
+ private lateinit var keyguardLayoutManager: KeyguardLayoutManager
+ @Mock lateinit var configurationController: ConfigurationController
+ @Mock lateinit var defaultLockscreenLayout: DefaultLockscreenLayout
+ @Mock lateinit var keyguardRootView: KeyguardRootView
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ whenever(defaultLockscreenLayout.id).thenReturn(DEFAULT)
+ keyguardLayoutManager =
+ KeyguardLayoutManager(
+ configurationController,
+ setOf(defaultLockscreenLayout),
+ keyguardRootView
+ )
+ }
+
+ @Test
+ fun testDefaultLayout() {
+ keyguardLayoutManager.transitionToLayout(DEFAULT)
+ verify(defaultLockscreenLayout).layoutViews(keyguardRootView)
+ }
+
+ @Test
+ fun testTransitionToLayout_validId() {
+ assertThat(keyguardLayoutManager.transitionToLayout(DEFAULT)).isTrue()
+ }
+ @Test
+ fun testTransitionToLayout_invalidId() {
+ assertThat(keyguardLayoutManager.transitionToLayout("abc")).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index a341346..c67f535 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -22,8 +22,18 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.util.mockito.mock
import com.google.common.collect.Range
@@ -47,7 +57,12 @@
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
- val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+ val interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor
underTest = DreamingToLockscreenTransitionViewModel(interactor, mock())
}
@@ -60,7 +75,7 @@
val job =
underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
- repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0f, STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
@@ -82,7 +97,7 @@
val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this)
// Should start running here...
- repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0f, STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.5f))
@@ -104,7 +119,7 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
- repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0f, STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.2f))
@@ -126,7 +141,7 @@
val job =
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
- repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0f, STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
@@ -138,13 +153,44 @@
job.cancel()
}
- private fun step(
- value: Float,
- state: TransitionState = TransitionState.RUNNING
- ): TransitionStep {
+ @Test
+ fun transitionEnded() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<TransitionStep>()
+
+ val job = underTest.transitionEnded.onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(TransitionStep(DOZING, DREAMING, 0.0f, STARTED))
+ repository.sendTransitionStep(TransitionStep(DOZING, DREAMING, 1.0f, FINISHED))
+
+ repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 0.0f, STARTED))
+ repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 0.1f, RUNNING))
+ repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 1.0f, FINISHED))
+
+ repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 0.0f, STARTED))
+ repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 0.5f, RUNNING))
+ repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 1.0f, FINISHED))
+
+ repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 0.0f, STARTED))
+ repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 0.5f, RUNNING))
+ repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 1.0f, CANCELED))
+
+ repository.sendTransitionStep(TransitionStep(DREAMING, AOD, 0.0f, STARTED))
+ repository.sendTransitionStep(TransitionStep(DREAMING, AOD, 1.0f, FINISHED))
+
+ assertThat(values.size).isEqualTo(3)
+ values.forEach {
+ assertThat(it.transitionState == FINISHED || it.transitionState == CANCELED)
+ .isTrue()
+ }
+
+ job.cancel()
+ }
+
+ private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
return TransitionStep(
- from = KeyguardState.DREAMING,
- to = KeyguardState.LOCKSCREEN,
+ from = DREAMING,
+ to = LOCKSCREEN,
value = value,
transitionState = state,
ownerName = "DreamingToLockscreenTransitionViewModelTest"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 694539b..75c8bff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
- val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+ val interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor
underTest = GoneToDreamingTransitionViewModel(interactor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 29886d5..d02b3fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -41,13 +41,12 @@
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
@@ -210,10 +209,10 @@
appContext = mContext,
scope = testScope.backgroundScope,
transitionInteractor =
- KeyguardTransitionInteractor(
- repository = FakeKeyguardTransitionRepository(),
- scope = testScope.backgroundScope
- ),
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ )
+ .keyguardTransitionInteractor,
repository = repository,
logger = UiEventLoggerFake(),
featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index ea17751..12fe07f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
- val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+ val interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor
underTest = LockscreenToDreamingTransitionViewModel(interactor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index bf56a98..83ae631 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
- val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+ val interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor
underTest = LockscreenToOccludedTransitionViewModel(interactor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index 34da26e..8860399 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
@Before
fun setUp() {
repository = FakeKeyguardTransitionRepository()
- val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+ val interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor
underTest = OccludedToLockscreenTransitionViewModel(interactor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index f88b71d..d8c78eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -22,7 +22,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -55,7 +55,12 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
repository = FakeKeyguardTransitionRepository()
- val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+ val interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor
underTest =
PrimaryBouncerToGoneTransitionViewModel(
interactor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
new file mode 100644
index 0000000..436c09c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsAodViewModelTest : SysuiTestCase() {
+ private val defaultPadding = 12
+ private lateinit var underTest: UdfpsAodViewModel
+
+ private lateinit var testScope: TestScope
+ private lateinit var configRepository: FakeConfigurationRepository
+ private lateinit var bouncerRepository: KeyguardBouncerRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var fakeCommandQueue: FakeCommandQueue
+ private lateinit var featureFlags: FakeFeatureFlags
+
+ @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding)
+ testScope = TestScope()
+ configRepository = FakeConfigurationRepository()
+ keyguardRepository = FakeKeyguardRepository()
+ bouncerRepository = FakeKeyguardBouncerRepository()
+ fakeCommandQueue = FakeCommandQueue()
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+ set(Flags.FACE_AUTH_REFACTOR, false)
+ }
+
+ val udfpsKeyguardInteractor =
+ UdfpsKeyguardInteractor(
+ configRepository,
+ BurnInInteractor(
+ context,
+ burnInHelper,
+ testScope.backgroundScope,
+ configRepository,
+ FakeSystemClock(),
+ ),
+ KeyguardInteractor(
+ keyguardRepository,
+ fakeCommandQueue,
+ featureFlags,
+ bouncerRepository,
+ configRepository,
+ ),
+ )
+
+ underTest =
+ UdfpsAodViewModel(
+ udfpsKeyguardInteractor,
+ context,
+ )
+ }
+
+ @Test
+ fun alphaAndVisibleUpdates_onDozeAmountChanges() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha)
+ val visible by collectLastValue(underTest.isVisible)
+
+ keyguardRepository.setDozeAmount(0f)
+ runCurrent()
+ assertThat(alpha).isEqualTo(0f)
+ assertThat(visible).isFalse()
+
+ keyguardRepository.setDozeAmount(.65f)
+ runCurrent()
+ assertThat(alpha).isEqualTo(.65f)
+ assertThat(visible).isTrue()
+
+ keyguardRepository.setDozeAmount(.23f)
+ runCurrent()
+ assertThat(alpha).isEqualTo(.23f)
+ assertThat(visible).isTrue()
+
+ keyguardRepository.setDozeAmount(1f)
+ runCurrent()
+ assertThat(alpha).isEqualTo(1f)
+ assertThat(visible).isTrue()
+ }
+
+ @Test
+ fun paddingUpdates_onScaleForResolutionChanges() =
+ testScope.runTest {
+ val padding by collectLastValue(underTest.padding)
+
+ configRepository.setScaleForResolution(1f)
+ runCurrent()
+ assertThat(padding).isEqualTo(defaultPadding)
+
+ configRepository.setScaleForResolution(2f)
+ runCurrent()
+ assertThat(padding).isEqualTo(defaultPadding * 2)
+
+ configRepository.setScaleForResolution(.5f)
+ runCurrent()
+ assertThat(padding).isEqualTo((defaultPadding * .5f).toInt())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
new file mode 100644
index 0000000..a30e2a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/** Tests UdfpsFingerprintViewModel specific flows. */
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsFingerprintViewModelTest : SysuiTestCase() {
+ private val defaultPadding = 12
+ private lateinit var underTest: FingerprintViewModel
+
+ private lateinit var testScope: TestScope
+ private lateinit var configRepository: FakeConfigurationRepository
+ private lateinit var bouncerRepository: KeyguardBouncerRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var fakeCommandQueue: FakeCommandQueue
+ private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+ @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding)
+ testScope = TestScope()
+ configRepository = FakeConfigurationRepository()
+ keyguardRepository = FakeKeyguardRepository()
+ bouncerRepository = FakeKeyguardBouncerRepository()
+ fakeCommandQueue = FakeCommandQueue()
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+ set(Flags.FACE_AUTH_REFACTOR, false)
+ }
+ bouncerRepository = FakeKeyguardBouncerRepository()
+ transitionRepository = FakeKeyguardTransitionRepository()
+ val transitionInteractor =
+ KeyguardTransitionInteractor(
+ transitionRepository,
+ testScope.backgroundScope,
+ )
+ val udfpsKeyguardInteractor =
+ UdfpsKeyguardInteractor(
+ configRepository,
+ BurnInInteractor(
+ context,
+ burnInHelper,
+ testScope.backgroundScope,
+ configRepository,
+ FakeSystemClock(),
+ ),
+ KeyguardInteractor(
+ keyguardRepository,
+ fakeCommandQueue,
+ featureFlags,
+ bouncerRepository,
+ configRepository,
+ ),
+ )
+
+ underTest =
+ FingerprintViewModel(
+ context,
+ transitionInteractor,
+ udfpsKeyguardInteractor,
+ )
+ }
+
+ @Test
+ fun paddingUpdates_onScaleForResolutionChanges() =
+ testScope.runTest {
+ val padding by collectLastValue(underTest.padding)
+
+ configRepository.setScaleForResolution(1f)
+ runCurrent()
+ assertThat(padding).isEqualTo(defaultPadding)
+
+ configRepository.setScaleForResolution(2f)
+ runCurrent()
+ assertThat(padding).isEqualTo(defaultPadding * 2)
+
+ configRepository.setScaleForResolution(.5f)
+ runCurrent()
+ assertThat(padding).isEqualTo((defaultPadding * .5).toInt())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
new file mode 100644
index 0000000..d58ceee
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.Utils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+/** Tests UDFPS lockscreen view model transitions. */
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsLockscreenViewModelTest : SysuiTestCase() {
+ private val lockscreenColorResId = android.R.attr.textColorPrimary
+ private val alternateBouncerResId = com.android.internal.R.attr.materialColorOnPrimaryFixed
+ private val lockscreenColor = Utils.getColorAttrDefaultColor(context, lockscreenColorResId)
+ private val alternateBouncerColor =
+ Utils.getColorAttrDefaultColor(context, alternateBouncerResId)
+
+ private lateinit var underTest: UdfpsLockscreenViewModel
+ private lateinit var testScope: TestScope
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testScope = TestScope()
+ transitionRepository = FakeKeyguardTransitionRepository()
+ val transitionInteractor =
+ KeyguardTransitionInteractor(
+ transitionRepository,
+ testScope.backgroundScope,
+ )
+ underTest =
+ UdfpsLockscreenViewModel(
+ context,
+ lockscreenColorResId,
+ alternateBouncerResId,
+ transitionInteractor,
+ )
+ }
+
+ @Test
+ fun goneToAodTransition() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: gone -> AOD
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "goneToAodTransition",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(visible).isFalse()
+
+ // TransitionState.RUNNING: gone -> AOD
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "goneToAodTransition",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(visible).isFalse()
+
+ // TransitionState.FINISHED: gone -> AOD
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "goneToAodTransition",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun lockscreenToAod() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: lockscreen -> AOD
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "lockscreenToAod",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.RUNNING: lockscreen -> AOD
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "lockscreenToAod",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.FINISHED: lockscreen -> AOD
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "lockscreenToAod",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun aodToLockscreen() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: AOD -> lockscreen
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "aodToLockscreen",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isFalse()
+
+ // TransitionState.RUNNING: AOD -> lockscreen
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "aodToLockscreen",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.FINISHED: AOD -> lockscreen
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "aodToLockscreen",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+ }
+
+ @Test
+ fun lockscreenToAlternateBouncer() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: lockscreen -> alternate bouncer
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "lockscreenToAlternateBouncer",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.RUNNING: lockscreen -> alternate bouncer
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "lockscreenToAlternateBouncer",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.FINISHED: lockscreen -> alternate bouncer
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "lockscreenToAlternateBouncer",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isTrue()
+ }
+
+ fun alternateBouncerToPrimaryBouncer() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: alternate bouncer -> primary bouncer
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "alternateBouncerToPrimaryBouncer",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.RUNNING: alternate bouncer -> primary bouncer
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "alternateBouncerToPrimaryBouncer",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.FINISHED: alternate bouncer -> primary bouncer
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "alternateBouncerToPrimaryBouncer",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isFalse()
+ }
+
+ fun alternateBouncerToAod() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: alternate bouncer -> aod
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.AOD,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "alternateBouncerToAod",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.RUNNING: alternate bouncer -> aod
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.AOD,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "alternateBouncerToAod",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.FINISHED: alternate bouncer -> aod
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "alternateBouncerToAod",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun lockscreenToOccluded() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: lockscreen -> occluded
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "lockscreenToOccluded",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.RUNNING: lockscreen -> occluded
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "lockscreenToOccluded",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.FINISHED: lockscreen -> occluded
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "lockscreenToOccluded",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(0f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun occludedToLockscreen() =
+ testScope.runTest {
+ val transition by collectLastValue(underTest.transition)
+ val visible by collectLastValue(underTest.visible)
+
+ // TransitionState.STARTED: occluded -> lockscreen
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "occludedToLockscreen",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.RUNNING: occluded -> lockscreen
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ value = .6f,
+ transitionState = TransitionState.RUNNING,
+ ownerName = "occludedToLockscreen",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+
+ // TransitionState.FINISHED: occluded -> lockscreen
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ ownerName = "occludedToLockscreen",
+ )
+ )
+ runCurrent()
+ assertThat(transition?.alpha).isEqualTo(1f)
+ assertThat(transition?.scale).isEqualTo(1f)
+ assertThat(transition?.color).isEqualTo(lockscreenColor)
+ assertThat(visible).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 56698e0..d1299d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -261,7 +261,6 @@
whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(SMARTSPACE_CREATION_TIME)
whenever(mediaSmartspaceTarget.expiryTimeMillis).thenReturn(SMARTSPACE_EXPIRY_TIME)
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
- whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true)
whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
index b40ebc9..91b0245 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
@@ -193,6 +193,17 @@
}
@Test
+ fun dozeWakeUpAnimationWaiting_inSplitShade_mediaIsHidden() {
+ val splitShadeContainer = FrameLayout(context)
+ keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
+ keyguardMediaController.useSplitShade = true
+
+ keyguardMediaController.isDozeWakeUpAnimationWaiting = true
+
+ assertThat(splitShadeContainer.visibility).isEqualTo(GONE)
+ }
+
+ @Test
fun dozing_inSingleShade_mediaIsVisible() {
val splitShadeContainer = FrameLayout(context)
keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
@@ -203,6 +214,17 @@
assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE)
}
+ @Test
+ fun dozeWakeUpAnimationWaiting_inSingleShade_mediaIsVisible() {
+ val splitShadeContainer = FrameLayout(context)
+ keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
+ keyguardMediaController.useSplitShade = false
+
+ keyguardMediaController.isDozeWakeUpAnimationWaiting = true
+
+ assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE)
+ }
+
private fun setDozing() {
whenever(statusBarStateController.isDozing).thenReturn(true)
statusBarStateListener.onDozingChanged(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index f902be3..b4b3073 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -233,7 +233,6 @@
FakeFeatureFlags().apply {
this.set(Flags.UMO_SURFACE_RIPPLE, false)
this.set(Flags.UMO_TURBULENCE_NOISE, false)
- this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true)
this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
}
@Mock private lateinit var globalSettings: GlobalSettings
@@ -1793,7 +1792,7 @@
// THEN it sends the PendingIntent without dismissing keyguard first,
// and does not use the Intent directly (see b/271845008)
captor.value.onClick(viewHolder.player)
- verify(pendingIntent).send()
+ verify(pendingIntent).send(any(Bundle::class.java))
verify(pendingIntent, never()).getIntent()
verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any())
}
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 7df54d4..e4f89a2 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
@@ -291,13 +291,13 @@
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
- assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
@@ -525,16 +525,16 @@
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo(TEST_CUSTOM_SUBTEXT);
assertThat(mViewHolder.mTwoLineTitleText.getText().toString()).isEqualTo(
TEST_DEVICE_NAME_1);
- assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isTrue();
+ assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isFalse();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
new file mode 100644
index 0000000..bcbf666
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import com.android.systemui.util.mockito.whenever
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() {
+
+ @Mock private lateinit var flags: FeatureFlags
+ @Mock private lateinit var coordinator: TaskSwitcherNotificationCoordinator
+
+ private lateinit var coreStartable: MediaProjectionTaskSwitcherCoreStartable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ coreStartable = MediaProjectionTaskSwitcherCoreStartable(coordinator, flags)
+ }
+
+ @Test
+ fun start_flagEnabled_startsCoordinator() {
+ whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(true)
+
+ coreStartable.start()
+
+ verify(coordinator).start()
+ }
+
+ @Test
+ fun start_flagDisabled_doesNotStartCoordinator() {
+ whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(false)
+
+ coreStartable.start()
+
+ verifyZeroInteractions(coordinator)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
new file mode 100644
index 0000000..83932b0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.data.repository
+
+import android.os.Binder
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() {
+
+ private val fakeActivityTaskManager = FakeActivityTaskManager()
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+
+ private val repo =
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = dispatcher
+ )
+
+ @Test
+ fun findRunningTaskFromWindowContainerToken_noMatch_returnsNull() {
+ fakeActivityTaskManager.addRunningTasks(createTask(taskId = 1), createTask(taskId = 2))
+
+ testScope.runTest {
+ val matchingTask =
+ repo.findRunningTaskFromWindowContainerToken(windowContainerToken = Binder())
+
+ assertThat(matchingTask).isNull()
+ }
+ }
+
+ @Test
+ fun findRunningTaskFromWindowContainerToken_matchingToken_returnsTaskInfo() {
+ val expectedToken = createToken()
+ val expectedTask = createTask(taskId = 1, token = expectedToken)
+
+ fakeActivityTaskManager.addRunningTasks(
+ createTask(taskId = 2),
+ expectedTask,
+ )
+
+ testScope.runTest {
+ val actualTask =
+ repo.findRunningTaskFromWindowContainerToken(
+ windowContainerToken = expectedToken.asBinder()
+ )
+
+ assertThat(actualTask).isEqualTo(expectedTask)
+ }
+ }
+
+ @Test
+ fun foregroundTask_returnsStreamOfTasksMovedToFront() =
+ testScope.runTest {
+ val foregroundTask by collectLastValue(repo.foregroundTask)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+ assertThat(foregroundTask?.taskId).isEqualTo(1)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 2))
+ assertThat(foregroundTask?.taskId).isEqualTo(2)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 3))
+ assertThat(foregroundTask?.taskId).isEqualTo(3)
+ }
+
+ @Test
+ fun foregroundTask_lastValueIsCached() =
+ testScope.runTest {
+ val foregroundTaskA by collectLastValue(repo.foregroundTask)
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+ assertThat(foregroundTaskA?.taskId).isEqualTo(1)
+
+ val foregroundTaskB by collectLastValue(repo.foregroundTask)
+ assertThat(foregroundTaskB?.taskId).isEqualTo(1)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
new file mode 100644
index 0000000..1c4870b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager
+import android.app.TaskStackListener
+import android.content.Intent
+import android.window.IWindowContainerToken
+import android.window.WindowContainerToken
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakeActivityTaskManager {
+
+ private val runningTasks = mutableListOf<RunningTaskInfo>()
+ private val taskTaskListeners = mutableListOf<TaskStackListener>()
+
+ val activityTaskManager = mock<ActivityTaskManager>()
+
+ init {
+ whenever(activityTaskManager.registerTaskStackListener(any())).thenAnswer {
+ taskTaskListeners += it.arguments[0] as TaskStackListener
+ return@thenAnswer Unit
+ }
+ whenever(activityTaskManager.unregisterTaskStackListener(any())).thenAnswer {
+ taskTaskListeners -= it.arguments[0] as TaskStackListener
+ return@thenAnswer Unit
+ }
+ whenever(activityTaskManager.getTasks(any())).thenAnswer {
+ val maxNumTasks = it.arguments[0] as Int
+ return@thenAnswer runningTasks.take(maxNumTasks)
+ }
+ }
+
+ fun moveTaskToForeground(task: RunningTaskInfo) {
+ taskTaskListeners.forEach { it.onTaskMovedToFront(task) }
+ }
+
+ fun addRunningTasks(vararg tasks: RunningTaskInfo) {
+ runningTasks += tasks
+ }
+
+ companion object {
+
+ fun createTask(
+ taskId: Int,
+ token: WindowContainerToken = createToken(),
+ baseIntent: Intent = Intent()
+ ) =
+ RunningTaskInfo().apply {
+ this.taskId = taskId
+ this.token = token
+ this.baseIntent = baseIntent
+ }
+
+ fun createToken(): WindowContainerToken {
+ val realToken = object : IWindowContainerToken.Stub() {}
+ return WindowContainerToken(realToken)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt
new file mode 100644
index 0000000..c59fd60
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.data.repository
+
+import android.app.TaskInfo
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeMediaProjectionRepository : MediaProjectionRepository {
+
+ private val state = MutableStateFlow<MediaProjectionState>(MediaProjectionState.NotProjecting)
+
+ fun switchProjectedTask(newTask: TaskInfo) {
+ state.value = MediaProjectionState.SingleTask(newTask)
+ }
+
+ override val mediaProjectionState: Flow<MediaProjectionState> = state.asStateFlow()
+
+ fun projectEntireScreen() {
+ state.value = MediaProjectionState.EntireScreen
+ }
+
+ fun stopProjecting() {
+ state.value = MediaProjectionState.NotProjecting
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
new file mode 100644
index 0000000..593e389
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Intent
+import android.os.IBinder
+import android.window.IWindowContainerToken
+import android.window.WindowContainerToken
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeTasksRepository : TasksRepository {
+
+ private val _foregroundTask = MutableStateFlow(DEFAULT_TASK)
+
+ override val foregroundTask: Flow<RunningTaskInfo> = _foregroundTask.asStateFlow()
+
+ private val runningTasks = mutableListOf(DEFAULT_TASK)
+
+ override suspend fun findRunningTaskFromWindowContainerToken(
+ windowContainerToken: IBinder
+ ): RunningTaskInfo? = runningTasks.firstOrNull { it.token.asBinder() == windowContainerToken }
+
+ fun addRunningTask(task: RunningTaskInfo) {
+ runningTasks.add(task)
+ }
+
+ fun moveTaskToForeground(task: RunningTaskInfo) {
+ _foregroundTask.value = task
+ }
+
+ companion object {
+ val DEFAULT_TASK = createTask(taskId = -1)
+ val LAUNCHER_INTENT: Intent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+
+ fun createTask(
+ taskId: Int,
+ token: WindowContainerToken = createToken(),
+ baseIntent: Intent = Intent()
+ ) =
+ RunningTaskInfo().apply {
+ this.taskId = taskId
+ this.token = token
+ this.baseIntent = baseIntent
+ }
+
+ fun createToken(): WindowContainerToken {
+ val realToken = object : IWindowContainerToken.Stub() {}
+ return WindowContainerToken(realToken)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
new file mode 100644
index 0000000..2b07465
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Binder
+import android.os.Handler
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.view.ContentRecordingSession
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
+
+ private val mediaProjectionManager = mock<MediaProjectionManager>()
+
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+ private val tasksRepo = FakeTasksRepository()
+
+ private lateinit var callback: MediaProjectionManager.Callback
+ private lateinit var repo: MediaProjectionManagerRepository
+
+ @Before
+ fun setUp() {
+ whenever(mediaProjectionManager.addCallback(any(), any())).thenAnswer {
+ callback = it.arguments[0] as MediaProjectionManager.Callback
+ return@thenAnswer Unit
+ }
+ repo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo
+ )
+ }
+
+ @Test
+ fun mediaProjectionState_onStart_emitsNotProjecting() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ callback.onStart(TEST_MEDIA_INFO)
+
+ assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+ }
+
+ @Test
+ fun mediaProjectionState_onStop_emitsNotProjecting() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ callback.onStop(TEST_MEDIA_INFO)
+
+ assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+ }
+
+ @Test
+ fun mediaProjectionState_onSessionSet_sessionNull_emitsNotProjecting() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, /* session= */ null)
+
+ assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+ }
+
+ @Test
+ fun mediaProjectionState_onSessionSet_contentToRecordDisplay_emitsEntireScreen() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ val session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+ assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+ }
+
+ @Test
+ fun mediaProjectionState_onSessionSet_tokenNull_emitsEntireScreen() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ val session =
+ ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null)
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+ assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+ }
+
+ @Test
+ fun mediaProjectionState_sessionSet_taskWithToken_noMatchingRunningTask_emitsEntireScreen() =
+ testScope.runTest {
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ val taskWindowContainerToken = Binder()
+ val session = ContentRecordingSession.createTaskSession(taskWindowContainerToken)
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+ assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+ }
+
+ @Test
+ fun mediaProjectionState_sessionSet_taskWithToken_matchingRunningTask_emitsSingleTask() =
+ testScope.runTest {
+ val token = FakeTasksRepository.createToken()
+ val task = FakeTasksRepository.createTask(taskId = 1, token = token)
+ tasksRepo.addRunningTask(task)
+ val state by collectLastValue(repo.mediaProjectionState)
+ runCurrent()
+
+ val session = ContentRecordingSession.createTaskSession(token.asBinder())
+ callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+
+ assertThat(state).isEqualTo(MediaProjectionState.SingleTask(task))
+ }
+
+ companion object {
+ val TEST_MEDIA_INFO =
+ MediaProjectionInfo(/* packageName= */ "com.test.package", UserHandle.CURRENT)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
new file mode 100644
index 0000000..112950b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.domain.interactor
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitchInteractorTest : SysuiTestCase() {
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+
+ private val fakeActivityTaskManager = FakeActivityTaskManager()
+ private val mediaRepo = FakeMediaProjectionRepository()
+ private val tasksRepo =
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = dispatcher
+ )
+
+ private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+
+ @Test
+ fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
+ testScope.runTest {
+ mediaRepo.stopProjecting()
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+
+ assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
+ }
+
+ @Test
+ fun taskSwitchChanges_projectingScreen_foregroundTaskChange_emitsNotProjectingTask() =
+ testScope.runTest {
+ mediaRepo.projectEntireScreen()
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+
+ assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
+ }
+
+ @Test
+ fun taskSwitchChanges_projectingTask_foregroundTaskDifferent_emitsTaskChanged() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(taskSwitchState)
+ .isEqualTo(
+ TaskSwitchState.TaskSwitched(
+ projectedTask = projectedTask,
+ foregroundTask = foregroundTask
+ )
+ )
+ }
+
+ @Test
+ fun taskSwitchChanges_projectingTask_foregroundTaskLauncher_emitsTaskUnchanged() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1, baseIntent = LAUNCHER_INTENT)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
+ }
+
+ @Test
+ fun taskSwitchChanges_projectingTask_foregroundTaskSame_emitsTaskUnchanged() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 0)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
+ }
+
+ companion object {
+ private val LAUNCHER_INTENT: Intent =
+ Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
new file mode 100644
index 0000000..ea44fb3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 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.mediaprojection.taskswitcher.ui.viewmodel
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+
+ private val fakeActivityTaskManager = FakeActivityTaskManager()
+ private val mediaRepo = FakeMediaProjectionRepository()
+ private val tasksRepo =
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = dispatcher
+ )
+ private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+
+ private val viewModel = TaskSwitcherNotificationViewModel(interactor)
+
+ @Test
+ fun uiState_notProjecting_emitsNotShowing() =
+ testScope.runTest {
+ mediaRepo.stopProjecting()
+ val uiState by collectLastValue(viewModel.uiState)
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_notProjecting_foregroundTaskChanged_emitsNotShowing() =
+ testScope.runTest {
+ mediaRepo.stopProjecting()
+ val uiState by collectLastValue(viewModel.uiState)
+
+ mediaRepo.switchProjectedTask(createTask(taskId = 1))
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_projectingEntireScreen_emitsNotShowing() =
+ testScope.runTest {
+ mediaRepo.projectEntireScreen()
+ val uiState by collectLastValue(viewModel.uiState)
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_projectingEntireScreen_foregroundTaskChanged_emitsNotShowing() =
+ testScope.runTest {
+ mediaRepo.projectEntireScreen()
+ val uiState by collectLastValue(viewModel.uiState)
+
+ mediaRepo.switchProjectedTask(createTask(taskId = 1))
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_projectingTask_foregroundTaskChanged_different_emitsShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ val foregroundTask = createTask(taskId = 2)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(uiState)
+ .isEqualTo(TaskSwitcherNotificationUiState.Showing(projectedTask, foregroundTask))
+ }
+
+ @Test
+ fun uiState_projectingTask_foregroundTaskChanged_same_emitsNotShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.moveTaskToForeground(projectedTask)
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
+ fun uiState_projectingTask_foregroundTaskChanged_different_taskIsLauncher_emitsNotShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ val foregroundTask = createTask(taskId = 2, baseIntent = LAUNCHER_INTENT)
+ mediaRepo.switchProjectedTask(projectedTask)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ companion object {
+ private val LAUNCHER_INTENT: Intent =
+ Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
index 9e54224..5890cbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
@@ -25,7 +25,7 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -97,10 +97,11 @@
multiShadeInteractor = interactor,
featureFlags = featureFlags,
keyguardTransitionInteractor =
- KeyguardTransitionInteractor(
- repository = keyguardTransitionRepository,
- scope = testScope.backgroundScope
- ),
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = keyguardTransitionRepository,
+ )
+ .keyguardTransitionInteractor,
falsingManager = falsingManager,
shadeController = shadeController,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
index 36b913f..bdb095a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
@@ -22,9 +22,9 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
import com.android.systemui.util.mockito.any
@@ -47,13 +47,9 @@
@Rule
@JvmField
val activityRule =
- ActivityTestRule<LaunchNotesRoleSettingsTrampolineActivity>(
- /* activityFactory= */ object :
- SingleActivityFactory<LaunchNotesRoleSettingsTrampolineActivity>(
- LaunchNotesRoleSettingsTrampolineActivity::class.java
- ) {
- override fun create(intent: Intent?) =
- LaunchNotesRoleSettingsTrampolineActivity(noteTaskController)
+ ActivityTestRule(
+ /* activityFactory= */ SingleActivityFactory {
+ LaunchNotesRoleSettingsTrampolineActivity(noteTaskController)
},
/* initialTouchMode= */ false,
/* launchActivity= */ false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
index 627c4a8..1f0f0d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
@@ -16,14 +16,13 @@
package com.android.systemui.notetask.shortcut
-import android.content.Intent
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.notetask.NoteTaskEntryPoint
import com.android.systemui.util.mockito.any
@@ -47,12 +46,8 @@
@JvmField
val activityRule =
ActivityTestRule<LaunchNoteTaskActivity>(
- /* activityFactory= */ object :
- SingleActivityFactory<LaunchNoteTaskActivity>(LaunchNoteTaskActivity::class.java) {
- override fun create(intent: Intent?) =
- LaunchNoteTaskActivity(
- controller = noteTaskController,
- )
+ /* activityFactory= */ SingleActivityFactory {
+ LaunchNoteTaskActivity(controller = noteTaskController)
},
/* initialTouchMode= */ false,
/* launchActivity= */ false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 962b537..22b1c7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -204,6 +204,19 @@
}
@Test
+ public void testLongClick_falsing() {
+ mFalsingManager.setFalseLongTap(true);
+ mTile.longClick(null /* view */);
+ mTestableLooper.processAllMessages();
+ assertThat(mTile.mLongClicked).isFalse();
+
+ mFalsingManager.setFalseLongTap(false);
+ mTile.longClick(null /* view */);
+ mTestableLooper.processAllMessages();
+ assertThat(mTile.mLongClicked).isTrue();
+ }
+
+ @Test
public void testSecondaryClick_Metrics() {
mTile.secondaryClick(null /* view */);
verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK)));
@@ -518,6 +531,7 @@
}
private static class TileImpl extends QSTileImpl<QSTile.BooleanState> {
boolean mClicked;
+ boolean mLongClicked;
int mRefreshes = 0;
protected TileImpl(
@@ -551,6 +565,11 @@
}
@Override
+ protected void handleLongClick(@Nullable View view) {
+ mLongClicked = true;
+ }
+
+ @Override
protected void handleUpdateState(BooleanState state, Object arg) {
mRefreshes++;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 7646193..16751c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.settings.brightness
-import android.content.Intent
import android.graphics.Rect
import android.os.Handler
import android.testing.AndroidTestingRunner
@@ -25,9 +24,9 @@
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
@@ -59,19 +58,17 @@
@JvmField
var activityRule =
ActivityTestRule(
- object : SingleActivityFactory<TestDialog>(TestDialog::class.java) {
- override fun create(intent: Intent?): TestDialog {
- return TestDialog(
- userTracker,
- displayTracker,
- brightnessSliderControllerFactory,
- mainExecutor,
- backgroundHandler
- )
- }
+ /* activityFactory= */ SingleActivityFactory {
+ TestDialog(
+ userTracker,
+ displayTracker,
+ brightnessSliderControllerFactory,
+ mainExecutor,
+ backgroundHandler
+ )
},
- false,
- false
+ /* initialTouchMode= */ false,
+ /* launchActivity= */ false,
)
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index ef7c7bc..9188293 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -80,6 +80,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.common.ui.view.LongPressHandlingView;
@@ -91,7 +92,6 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewConfigurator;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -629,7 +629,7 @@
mHeadsUpManager);
mNotificationPanelViewController.setTrackingStartedListener(() -> {});
mNotificationPanelViewController.setOpenCloseListener(
- new NotificationPanelViewController.OpenCloseListener() {
+ new OpenCloseListener() {
@Override
public void onClosingFinished() {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 5802eb3..eb4ae1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -514,6 +514,17 @@
}
@Test
+ public void keyguardStatusView_willPlayDelayedDoze_notifiesKeyguardMediaController() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true);
+
+ verify(mKeyguardMediaController).setDozeWakeUpAnimationWaiting(true);
+ }
+
+ @Test
public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() {
when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 2a9b403..5fb3a79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -28,6 +28,11 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
+import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
+import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.dock.DockManager
@@ -35,14 +40,9 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
-import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
-import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.log.BouncerLogger
import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
@@ -80,9 +80,9 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
import java.util.Optional
+import org.mockito.Mockito.`when` as whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -198,10 +198,9 @@
multiShadeInteractor = multiShadeInteractor,
featureFlags = featureFlags,
keyguardTransitionInteractor =
- KeyguardTransitionInteractor(
- repository = FakeKeyguardTransitionRepository(),
- scope = testScope.backgroundScope
- ),
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ ).keyguardTransitionInteractor,
falsingManager = FalsingManagerFake(),
shadeController = shadeController,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 252a03b..544137e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -40,8 +40,8 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.log.BouncerLogger
import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
@@ -211,10 +211,10 @@
multiShadeInteractor = multiShadeInteractor,
featureFlags = featureFlags,
keyguardTransitionInteractor =
- KeyguardTransitionInteractor(
- repository = FakeKeyguardTransitionRepository(),
- scope = testScope.backgroundScope
- ),
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ )
+ .keyguardTransitionInteractor,
falsingManager = FalsingManagerFake(),
shadeController = shadeController,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 00a0567..729c4a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -56,7 +56,7 @@
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var assistManager: AssistManager
@Mock private lateinit var gutsManager: NotificationGutsManager
- @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
+ @Mock private lateinit var shadeViewController: ShadeViewController
@Mock private lateinit var nswvc: NotificationShadeWindowViewController
@Mock private lateinit var display: Display
@@ -82,7 +82,7 @@
Lazy { gutsManager },
)
shadeController.setNotificationShadeWindowViewController(nswvc)
- shadeController.setNotificationPanelViewController(notificationPanelViewController)
+ shadeController.setShadeViewController(shadeViewController)
}
@Test
@@ -91,9 +91,9 @@
// Trying to open it does nothing.
shadeController.animateExpandShade()
- verify(notificationPanelViewController, never()).expandToNotifications()
+ verify(shadeViewController, never()).expandToNotifications()
shadeController.animateExpandQs()
- verify(notificationPanelViewController, never()).expand(ArgumentMatchers.anyBoolean())
+ verify(shadeViewController, never()).expand(ArgumentMatchers.anyBoolean())
}
@Test
@@ -102,15 +102,15 @@
// Can now be opened.
shadeController.animateExpandShade()
- verify(notificationPanelViewController).expandToNotifications()
+ verify(shadeViewController).expandToNotifications()
shadeController.animateExpandQs()
- verify(notificationPanelViewController).expandToQs()
+ verify(shadeViewController).expandToQs()
}
@Test
fun cancelExpansionAndCollapseShade_callsCancelCurrentTouch() {
// GIVEN the shade is tracking a touch
- whenever(notificationPanelViewController.isTracking).thenReturn(true)
+ whenever(shadeViewController.isTracking).thenReturn(true)
// WHEN cancelExpansionAndCollapseShade is called
shadeController.cancelExpansionAndCollapseShade()
@@ -122,7 +122,7 @@
@Test
fun cancelExpansionAndCollapseShade_doesNotCallAnimateCollapseShade_whenCollapsed() {
// GIVEN the shade is tracking a touch
- whenever(notificationPanelViewController.isTracking).thenReturn(false)
+ whenever(shadeViewController.isTracking).thenReturn(false)
// WHEN cancelExpansionAndCollapseShade is called
shadeController.cancelExpansionAndCollapseShade()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index 4524797..3ea8f54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -84,6 +84,7 @@
MockitoAnnotations.initMocks(this)
featureFlags.set(Flags.FACE_AUTH_REFACTOR, false)
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val refreshUsersScheduler =
RefreshUsersScheduler(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 2fbe871..ea70e9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -32,7 +32,6 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -46,11 +45,14 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineScheduler
@@ -62,9 +64,8 @@
import org.mockito.ArgumentMatchers.same
import org.mockito.Mockito.anyString
import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import java.util.function.Consumer
-import kotlin.time.Duration.Companion.seconds
import org.mockito.Mockito.`when` as whenever
@SmallTest
@@ -75,7 +76,6 @@
private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
private val keyguardRepository = FakeKeyguardRepository()
private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- private val notifPipelineFlags: NotifPipelineFlags = mock()
private val notifPipeline: NotifPipeline = mock()
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
private val statusBarStateController: StatusBarStateController = mock()
@@ -136,13 +136,8 @@
)
testScheduler.runCurrent()
- // WHEN: The shade is expanded
- whenever(statusBarStateController.isExpanded).thenReturn(true)
- statusBarStateListener.onExpandedChanged(true)
- testScheduler.runCurrent()
-
- // THEN: The notification is still treated as "unseen" and is not filtered out.
- assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ // THEN: We are no longer listening for shade expansions
+ verify(statusBarStateController, never()).addCallback(any())
}
}
@@ -152,6 +147,10 @@
keyguardRepository.setKeyguardShowing(false)
whenever(statusBarStateController.isExpanded).thenReturn(false)
runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+
// WHEN: A notification is posted
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
@@ -162,6 +161,9 @@
// WHEN: The keyguard is now showing
keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD)
+ )
testScheduler.runCurrent()
// THEN: The notification is recognized as "seen" and is filtered out.
@@ -169,6 +171,9 @@
// WHEN: The keyguard goes away
keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ )
testScheduler.runCurrent()
// THEN: The notification is shown regardless
@@ -182,9 +187,10 @@
keyguardRepository.setKeyguardShowing(false)
whenever(statusBarStateController.isExpanded).thenReturn(true)
runKeyguardCoordinatorTest {
- val fakeEntry = NotificationEntryBuilder()
+ val fakeEntry =
+ NotificationEntryBuilder()
.setNotification(Notification.Builder(mContext, "id").setOngoing(true).build())
- .build()
+ .build()
collectionListener.onEntryAdded(fakeEntry)
// WHEN: The keyguard is now showing
@@ -202,11 +208,13 @@
keyguardRepository.setKeyguardShowing(false)
whenever(statusBarStateController.isExpanded).thenReturn(true)
runKeyguardCoordinatorTest {
- val fakeEntry = NotificationEntryBuilder().build().apply {
- row = mock<ExpandableNotificationRow>().apply {
- whenever(isMediaRow).thenReturn(true)
+ val fakeEntry =
+ NotificationEntryBuilder().build().apply {
+ row =
+ mock<ExpandableNotificationRow>().apply {
+ whenever(isMediaRow).thenReturn(true)
+ }
}
- }
collectionListener.onEntryAdded(fakeEntry)
// WHEN: The keyguard is now showing
@@ -299,14 +307,12 @@
runKeyguardCoordinatorTest {
// WHEN: A new notification is posted
val fakeSummary = NotificationEntryBuilder().build()
- val fakeChild = NotificationEntryBuilder()
+ val fakeChild =
+ NotificationEntryBuilder()
.setGroup(context, "group")
.setGroupSummary(context, false)
.build()
- GroupEntryBuilder()
- .setSummary(fakeSummary)
- .addChild(fakeChild)
- .build()
+ GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build()
collectionListener.onEntryAdded(fakeSummary)
collectionListener.onEntryAdded(fakeChild)
@@ -331,6 +337,10 @@
runKeyguardCoordinatorTest {
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
// WHEN: five seconds have passed
testScheduler.advanceTimeBy(5.seconds)
@@ -338,10 +348,16 @@
// WHEN: Keyguard is no longer showing
keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
testScheduler.runCurrent()
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD)
+ )
testScheduler.runCurrent()
// THEN: The notification is now recognized as "seen" and is filtered out.
@@ -354,11 +370,17 @@
// GIVEN: Keyguard is showing, unseen notification is present
keyguardRepository.setKeyguardShowing(true)
runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
// WHEN: Keyguard is no longer showing
keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
@@ -369,14 +391,212 @@
}
}
+ @Test
+ fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() {
+ // GIVEN: Keyguard is showing, not dozing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ val firstEntry = NotificationEntryBuilder().setId(1).build()
+ collectionListener.onEntryAdded(firstEntry)
+ testScheduler.runCurrent()
+
+ // WHEN: one second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: another unseen notification is posted
+ val secondEntry = NotificationEntryBuilder().setId(2).build()
+ collectionListener.onEntryAdded(secondEntry)
+ testScheduler.runCurrent()
+
+ // WHEN: four more seconds have passed
+ testScheduler.advanceTimeBy(4.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // THEN: The first notification is considered seen and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue()
+
+ // THEN: The second notification is still considered unseen and is not filtered out
+ assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() {
+ // GIVEN: Keyguard is showing, not dozing
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: a new notification is posted
+ val entry = NotificationEntryBuilder().setId(1).build()
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: five more seconds have passed
+ testScheduler.advanceTimeBy(5.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is removed
+ collectionListener.onEntryRemoved(entry, 0)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is re-posted
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: one more second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // THEN: The notification is considered unseen and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() {
+ // GIVEN: Keyguard is showing, not dozing
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: a new notification is posted
+ val entry = NotificationEntryBuilder().setId(1).build()
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: one second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is removed
+ collectionListener.onEntryRemoved(entry, 0)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is re-posted
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: one more second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // THEN: The notification is considered unseen and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() {
+ // GIVEN: Keyguard is showing, not dozing
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: a new notification is posted
+ val entry = NotificationEntryBuilder().setId(1).build()
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: one second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is updated
+ collectionListener.onEntryUpdated(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: four more seconds have passed
+ testScheduler.advanceTimeBy(4.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // THEN: The notification is considered unseen and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+ }
+ }
+
private fun runKeyguardCoordinatorTest(
testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
) {
val testDispatcher = UnconfinedTestDispatcher()
val testScope = TestScope(testDispatcher)
- val fakeSettings = FakeSettings().apply {
- putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
- }
+ val fakeSettings =
+ FakeSettings().apply {
+ putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+ }
val seenNotificationsProvider = SeenNotificationsProviderImpl()
val keyguardCoordinator =
KeyguardCoordinator(
@@ -387,7 +607,6 @@
keyguardRepository,
keyguardTransitionRepository,
mock<KeyguardCoordinatorLogger>(),
- notifPipelineFlags,
testScope.backgroundScope,
sectionHeaderVisibilityProvider,
fakeSettings,
@@ -397,11 +616,12 @@
keyguardCoordinator.attach(notifPipeline)
testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
KeyguardCoordinatorTestScope(
- keyguardCoordinator,
- testScope,
- seenNotificationsProvider,
- fakeSettings,
- ).testBlock()
+ keyguardCoordinator,
+ testScope,
+ seenNotificationsProvider,
+ fakeSettings,
+ )
+ .testBlock()
}
}
@@ -414,10 +634,9 @@
val testScheduler: TestCoroutineScheduler
get() = scope.testScheduler
- val onStateChangeListener: Consumer<String> =
- withArgCaptor {
- verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
- }
+ val onStateChangeListener: Consumer<String> = withArgCaptor {
+ verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+ }
val unseenFilter: NotifFilter
get() = keyguardCoordinator.unseenNotifFilter
@@ -426,11 +645,11 @@
verify(notifPipeline).addCollectionListener(capture())
}
- val onHeadsUpChangedListener: OnHeadsUpChangedListener get() =
- withArgCaptor { verify(headsUpManager).addListener(capture()) }
+ val onHeadsUpChangedListener: OnHeadsUpChangedListener
+ get() = withArgCaptor { verify(headsUpManager).addListener(capture()) }
- val statusBarStateListener: StatusBarStateController.StateListener get() =
- withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
+ val statusBarStateListener: StatusBarStateController.StateListener
+ get() = withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
get() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 6ae7dca..ee8325e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -87,6 +87,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
+import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -415,6 +416,36 @@
}
@Test
+ public void callSwipeCallbacksDuringClearAll() {
+ initController(/* viewIsAttached= */ true);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ NotificationCallback notificationCallback = mController.mNotificationCallback;
+
+ when(mNotificationStackScrollLayout.getClearAllInProgress()).thenReturn(true);
+
+ notificationCallback.onBeginDrag(row);
+ verify(mNotificationStackScrollLayout).onSwipeBegin(row);
+
+ notificationCallback.handleChildViewDismissed(row);
+ verify(mNotificationStackScrollLayout).onSwipeEnd();
+ }
+
+ @Test
+ public void callSwipeCallbacksDuringClearNotification() {
+ initController(/* viewIsAttached= */ true);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ NotificationCallback notificationCallback = mController.mNotificationCallback;
+
+ when(mNotificationStackScrollLayout.getClearAllInProgress()).thenReturn(false);
+
+ notificationCallback.onBeginDrag(row);
+ verify(mNotificationStackScrollLayout).onSwipeBegin(row);
+
+ notificationCallback.handleChildViewDismissed(row);
+ verify(mNotificationStackScrollLayout).onSwipeEnd();
+ }
+
+ @Test
public void testOnMenuClickedLogging() {
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 1ffffe4..88d8dfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -450,7 +450,7 @@
() -> mAssistManager,
() -> mNotificationGutsManager
));
- mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+ mShadeController.setShadeViewController(mNotificationPanelViewController);
mShadeController.setNotificationShadeWindowViewController(
mNotificationShadeWindowViewController);
mShadeController.setNotificationPresenter(mNotificationPresenter);
@@ -490,9 +490,11 @@
mMetricsLogger,
mShadeLogger,
mUiBgExecutor,
+ mNotificationPanelViewController,
mNotificationMediaManager,
mLockscreenUserManager,
mRemoteInputManager,
+ mQuickSettingsController,
mUserSwitcherController,
mBatteryController,
mColorExtractor,
@@ -587,8 +589,6 @@
// TODO: we should be able to call mCentralSurfaces.start() and have all the below values
// initialized automatically and make NPVC private.
mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView;
- mCentralSurfaces.mShadeSurface = mNotificationPanelViewController;
- mCentralSurfaces.mQsController = mQuickSettingsController;
mCentralSurfaces.mDozeScrimController = mDozeScrimController;
mCentralSurfaces.mPresenter = mNotificationPresenter;
mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt
new file mode 100644
index 0000000..47671fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 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.phone
+
+import android.app.WallpaperManager
+import android.content.pm.UserInfo
+import android.os.Looper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.os.FakeHandler
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class LockscreenWallpaperTest : SysuiTestCase() {
+
+ private lateinit var underTest: LockscreenWallpaper
+
+ private val testScope = TestScope(StandardTestDispatcher())
+ private val userRepository = FakeUserRepository()
+
+ private val wallpaperManager: WallpaperManager = mock()
+
+ @Before
+ fun setUp() {
+ whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false)
+ whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
+ underTest =
+ LockscreenWallpaper(
+ /* wallpaperManager= */ wallpaperManager,
+ /* iWallpaperManager= */ mock(),
+ /* keyguardUpdateMonitor= */ mock(),
+ /* dumpManager= */ mock(),
+ /* mediaManager= */ mock(),
+ /* mainHandler= */ FakeHandler(Looper.getMainLooper()),
+ /* javaAdapter= */ JavaAdapter(testScope.backgroundScope),
+ /* userRepository= */ userRepository,
+ /* userTracker= */ mock(),
+ )
+ underTest.start()
+ }
+
+ @Test
+ fun getBitmap_matchesUserIdFromUserRepo() =
+ testScope.runTest {
+ val info = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0)
+ userRepository.setUserInfos(listOf(info))
+ userRepository.setSelectedUserInfo(info)
+
+ underTest.bitmap
+
+ verify(wallpaperManager).getWallpaperFile(any(), eq(5))
+ }
+
+ @Test
+ fun getBitmap_usesOldUserIfNewUserInProgress() =
+ testScope.runTest {
+ val info5 = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0)
+ val info6 = UserInfo(/* id= */ 6, /* name= */ "id6", /* flags= */ 0)
+ userRepository.setUserInfos(listOf(info5, info6))
+ userRepository.setSelectedUserInfo(info5)
+
+ // WHEN the selection of user 6 is only in progress
+ userRepository.setSelectedUserInfo(
+ info6,
+ selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS
+ )
+
+ underTest.bitmap
+
+ // THEN we still use user 5 for wallpaper selection
+ verify(wallpaperManager).getWallpaperFile(any(), eq(5))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index 6301fa0..842d548 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -20,7 +20,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -48,7 +48,11 @@
keyguardTransitionRepository = FakeKeyguardTransitionRepository()
val interactor =
- KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = keyguardTransitionRepository,
+ )
+ .keyguardTransitionInteractor
underTest = CollapsedStatusBarViewModelImpl(interactor, testScope.backgroundScope)
}
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 b30c20d..b04eb01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
@@ -25,8 +25,8 @@
import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
import com.google.common.truth.Truth.assertThat
import javax.inject.Inject
@@ -49,19 +49,17 @@
open class UsbPermissionActivityTestable @Inject constructor (
val message: UsbAudioWarningDialogMessage
- )
- : UsbPermissionActivity(UsbAudioWarningDialogMessage())
+ ) : UsbPermissionActivity(UsbAudioWarningDialogMessage())
@Rule
@JvmField
- var activityRule = ActivityTestRule<UsbPermissionActivityTestable>(
- object : SingleActivityFactory<UsbPermissionActivityTestable>(
- UsbPermissionActivityTestable::class.java
- ) {
- override fun create(intent: Intent?): UsbPermissionActivityTestable {
- return UsbPermissionActivityTestable(mMessage)
- }
- }, false, false)
+ var activityRule = ActivityTestRule(
+ /* activityFactory= */ SingleActivityFactory {
+ UsbPermissionActivityTestable(mMessage)
+ },
+ /* initialTouchMode= */ false,
+ /* launchActivity= */ false,
+ )
private val activityIntent = Intent(mContext, UsbPermissionActivityTestable::class.java)
.apply {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 079fbcd..0c28cbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -26,6 +26,8 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
@@ -224,6 +226,40 @@
}
@Test
+ fun userTrackerCallback_updatesSelectionStatus() = runSelfCancelingTest {
+ underTest = create(this)
+ var selectedUser: SelectedUserModel? = null
+ underTest.selectedUser.onEach { selectedUser = it }.launchIn(this)
+ setUpUsers(count = 2, selectedIndex = 1)
+
+ // WHEN the user is changing
+ tracker.onUserChanging(userId = 1)
+
+ // THEN the selection status is IN_PROGRESS
+ assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+
+ // WHEN the user has finished changing
+ tracker.onUserChanged(userId = 1)
+
+ // THEN the selection status is COMPLETE
+ assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE)
+
+ tracker.onProfileChanged()
+ assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE)
+
+ setUpUsers(count = 2, selectedIndex = 0)
+
+ tracker.onUserChanging(userId = 0)
+ assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+
+ // WHEN a profile change occurs while a user is changing
+ tracker.onProfileChanged()
+
+ // THEN the selection status remains as IN_PROGRESS
+ assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+ }
+
+ @Test
fun userSwitchingInProgress_registersUserTrackerCallback() = runSelfCancelingTest {
underTest = create(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index a09af00..ef12b2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -39,6 +39,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -153,6 +154,7 @@
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.Transitions;
import org.junit.After;
import org.junit.Before;
@@ -282,7 +284,7 @@
@Mock
private ScreenOffAnimationController mScreenOffAnimationController;
@Mock
- private TaskViewTransitions mTaskViewTransitions;
+ Transitions mTransitions;
@Mock
private Optional<OneHandedController> mOneHandedOptional;
@Mock
@@ -294,6 +296,8 @@
@Mock
private Icon mAppBubbleIcon;
+ private TaskViewTransitions mTaskViewTransitions;
+
private TestableBubblePositioner mPositioner;
private BubbleData mBubbleData;
@@ -309,6 +313,12 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ doReturn(true).when(mTransitions).isRegistered();
+ }
+ mTaskViewTransitions = new TaskViewTransitions(mTransitions);
+
mTestableLooper = TestableLooper.get(this);
// For the purposes of this test, just run everything synchronously
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt
new file mode 100644
index 0000000..5a92fb1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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.activity
+
+import android.app.Activity
+import android.content.Intent
+import androidx.test.runner.intercepting.SingleActivityFactory
+
+/**
+ * Builds a new [SingleActivityFactory] which delegating any call of [SingleActivityFactory.create]
+ * to the [instantiate] parameter.
+ *
+ * For more details, see [SingleActivityFactory].
+ */
+inline fun <reified T : Activity> SingleActivityFactory(
+ crossinline instantiate: (intent: Intent?) -> T,
+): SingleActivityFactory<T> {
+ return object : SingleActivityFactory<T>(T::class.java) {
+ override fun create(intent: Intent?): T = instantiate(intent)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
new file mode 100644
index 0000000..312ade5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Helper to create a new KeyguardTransitionInteractor in a way that doesn't require modifying 20+
+ * tests whenever we add a constructor param.
+ */
+object KeyguardTransitionInteractorFactory {
+ @JvmOverloads
+ @JvmStatic
+ fun create(
+ scope: CoroutineScope,
+ repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(),
+ ): WithDependencies {
+ return WithDependencies(
+ repository = repository,
+ KeyguardTransitionInteractor(
+ scope = scope,
+ repository = repository,
+ )
+ )
+ }
+
+ data class WithDependencies(
+ val repository: KeyguardTransitionRepository,
+ val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 61e5b5f..51ee0c0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -19,18 +19,28 @@
import android.content.pm.UserInfo
import android.os.UserHandle
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.yield
class FakeUserRepository : UserRepository {
companion object {
// User id to represent a non system (human) user id. We presume this is the main user.
private const val MAIN_USER_ID = 10
+
+ private val DEFAULT_SELECTED_USER = 0
+ private val DEFAULT_SELECTED_USER_INFO =
+ UserInfo(
+ /* id= */ DEFAULT_SELECTED_USER,
+ /* name= */ "default selected user",
+ /* flags= */ 0,
+ )
}
private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
@@ -40,8 +50,11 @@
private val _userInfos = MutableStateFlow<List<UserInfo>>(emptyList())
override val userInfos: Flow<List<UserInfo>> = _userInfos.asStateFlow()
- private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
- override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
+ override val selectedUser =
+ MutableStateFlow(
+ SelectedUserModel(DEFAULT_SELECTED_USER_INFO, SelectionStatus.SELECTION_COMPLETE)
+ )
+ override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
private val _userSwitchingInProgress = MutableStateFlow(false)
override val userSwitchingInProgress: Flow<Boolean>
@@ -72,7 +85,7 @@
}
override fun getSelectedUserInfo(): UserInfo {
- return checkNotNull(_selectedUserInfo.value)
+ return selectedUser.value.userInfo
}
override fun isSimpleUserSwitcher(): Boolean {
@@ -87,12 +100,15 @@
_userInfos.value = infos
}
- suspend fun setSelectedUserInfo(userInfo: UserInfo) {
+ suspend fun setSelectedUserInfo(
+ userInfo: UserInfo,
+ selectionStatus: SelectionStatus = SelectionStatus.SELECTION_COMPLETE,
+ ) {
check(_userInfos.value.contains(userInfo)) {
"Cannot select the following user, it is not in the list of user infos: $userInfo!"
}
- _selectedUserInfo.value = userInfo
+ selectedUser.value = SelectedUserModel(userInfo, selectionStatus)
yield()
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index b1cdc50..ba3d434 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -319,6 +319,10 @@
FullScreenMagnificationController::onUserContextChanged,
FullScreenMagnificationController.this, mDisplayId);
mControllerCtx.getHandler().sendMessage(m);
+
+ synchronized (mLock) {
+ refreshThumbnail();
+ }
}
@Override
@@ -344,7 +348,7 @@
mMagnificationRegion.set(magnified);
mMagnificationRegion.getBounds(mMagnificationBounds);
- refreshThumbnail(getScale(), getCenterX(), getCenterY());
+ refreshThumbnail();
// It's possible that our magnification spec is invalid with the new bounds.
// Adjust the current spec's offsets if necessary.
@@ -602,13 +606,13 @@
}
@GuardedBy("mLock")
- void refreshThumbnail(float scale, float centerX, float centerY) {
+ void refreshThumbnail() {
if (mMagnificationThumbnail != null) {
mMagnificationThumbnail.setThumbnailBounds(
mMagnificationBounds,
- scale,
- centerX,
- centerY
+ getScale(),
+ getCenterX(),
+ getCenterY()
);
}
}
@@ -627,7 +631,7 @@
// We call refreshThumbnail when the thumbnail is just created to set current
// magnification bounds to thumbnail. It to prevent the thumbnail size has not yet
// updated properly and thus shows with huge size. (b/276314641)
- refreshThumbnail(getScale(), getCenterX(), getCenterY());
+ refreshThumbnail();
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 87fbee7..f6948e9 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -502,6 +502,10 @@
@Override
public void onSourceBoundsChanged(int displayId, Rect bounds) {
if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_WINDOW)) {
+ // notify sysui the magnification scale changed on window magnifier
+ mWindowMagnificationMgr.onUserMagnificationScaleChanged(
+ mUserId, displayId, getWindowMagnificationMgr().getScale(displayId));
+
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_WINDOW)
.setActivated(getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId))
@@ -516,6 +520,10 @@
public void onFullScreenMagnificationChanged(int displayId, @NonNull Region region,
@NonNull MagnificationConfig config) {
if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_FULLSCREEN)) {
+ // notify sysui the magnification scale changed on fullscreen magnifier
+ mWindowMagnificationMgr.onUserMagnificationScaleChanged(
+ mUserId, displayId, config.getScale());
+
mAms.notifyMagnificationChanged(displayId, region, config);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
index 03fa93d..a7bdd5a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
@@ -99,15 +99,17 @@
Log.d(LOG_TAG, "setThumbnailBounds " + currentBounds);
}
mHandler.post(() -> {
- mWindowBounds = currentBounds;
- setBackgroundBounds();
+ refreshBackgroundBounds(currentBounds);
if (mVisible) {
updateThumbnailMainThread(scale, centerX, centerY);
}
});
}
- private void setBackgroundBounds() {
+ @MainThread
+ private void refreshBackgroundBounds(Rect currentBounds) {
+ mWindowBounds = currentBounds;
+
Point magnificationBoundary = getMagnificationThumbnailPadding(mContext);
mThumbnailWidth = (int) (mWindowBounds.width() / BG_ASPECT_RATIO);
mThumbnailHeight = (int) (mWindowBounds.height() / BG_ASPECT_RATIO);
@@ -117,6 +119,10 @@
mBackgroundParams.height = mThumbnailHeight;
mBackgroundParams.x = initX;
mBackgroundParams.y = initY;
+
+ if (mVisible) {
+ mWindowManager.updateViewLayout(mThumbnailLayout, mBackgroundParams);
+ }
}
@MainThread
@@ -264,21 +270,16 @@
mThumbnailView.setScaleX(scaleDown);
mThumbnailView.setScaleY(scaleDown);
}
- float thumbnailWidth;
- float thumbnailHeight;
- if (mThumbnailView.getWidth() == 0 || mThumbnailView.getHeight() == 0) {
- // if the thumbnail view size is not updated correctly, we just use the cached values.
- thumbnailWidth = mThumbnailWidth;
- thumbnailHeight = mThumbnailHeight;
- } else {
- thumbnailWidth = mThumbnailView.getWidth();
- thumbnailHeight = mThumbnailView.getHeight();
- }
- if (!Float.isNaN(centerX)) {
+
+ if (!Float.isNaN(centerX)
+ && !Float.isNaN(centerY)
+ && mThumbnailWidth > 0
+ && mThumbnailHeight > 0
+ ) {
var padding = mThumbnailView.getPaddingTop();
var ratio = 1f / BG_ASPECT_RATIO;
- var centerXScaled = centerX * ratio - (thumbnailWidth / 2f + padding);
- var centerYScaled = centerY * ratio - (thumbnailHeight / 2f + padding);
+ var centerXScaled = centerX * ratio - (mThumbnailWidth / 2f + padding);
+ var centerYScaled = centerY * ratio - (mThumbnailHeight / 2f + padding);
if (DEBUG) {
Log.d(
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
index 1202cfa..6c6394f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
@@ -201,6 +201,22 @@
return true;
}
+ boolean onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
+ if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+ mTrace.logTrace(TAG + ".onMagnificationScaleUpdated",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId);
+ }
+ try {
+ mConnection.onUserMagnificationScaleChanged(userId, displayId, scale);
+ } catch (RemoteException e) {
+ if (DBG) {
+ Slog.e(TAG, "Error calling onMagnificationScaleUpdated()", e);
+ }
+ return false;
+ }
+ return true;
+ }
+
boolean setConnectionCallback(IWindowMagnificationConnectionCallback connectionCallback) {
if (mTrace.isA11yTracingEnabledForTypes(
FLAGS_WINDOW_MAGNIFICATION_CONNECTION
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index d07db3f..d96682c 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -827,6 +827,20 @@
}
/**
+ * Notify System UI the magnification scale on the specified display for userId is changed.
+ *
+ * @param userId the user id.
+ * @param displayId the logical display id.
+ * @param scale magnification scale.
+ */
+ public boolean onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
+ synchronized (mLock) {
+ return mConnectionWrapper != null
+ && mConnectionWrapper.onUserMagnificationScaleChanged(userId, displayId, scale);
+ }
+ }
+
+ /**
* Returns the screen-relative X coordinate of the center of the magnified bounds.
*
* @param displayId The logical display id
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 76a994e..99c2f8a 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1972,7 +1972,7 @@
}
private void dismissUserSwitchDialog(Runnable onDismissed) {
- mInjector.dismissUserSwitchingDialog(onDismissed);
+ mUiHandler.post(() -> mInjector.dismissUserSwitchingDialog(onDismissed));
}
private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 29a1941..393e430 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -30,6 +30,8 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.BluetoothProfileConnectionInfo;
@@ -289,37 +291,38 @@
* @param on
* @param eventSource for logging purposes
*/
- /*package*/ void setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) {
+ /*package*/ void setSpeakerphoneOn(
+ IBinder cb, int uid, boolean on, boolean isPrivileged, String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid);
+ Log.v(TAG, "setSpeakerphoneOn, on: " + on + " uid: " + uid);
}
postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
- cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
- on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
+ cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+ on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false, isPrivileged));
}
/**
* Select device for use for communication use cases.
* @param cb Client binder for death detection
- * @param pid Client pid
+ * @param uid Client uid
* @param device Device selected or null to unselect.
* @param eventSource for logging purposes
*/
private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000;
- /*package*/ boolean setCommunicationDevice(
- IBinder cb, int pid, AudioDeviceInfo device, String eventSource) {
+ /*package*/ boolean setCommunicationDevice(IBinder cb, int uid, AudioDeviceInfo device,
+ boolean isPrivileged, String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "setCommunicationDevice, device: " + device + ", pid: " + pid);
+ Log.v(TAG, "setCommunicationDevice, device: " + device + ", uid: " + uid);
}
AudioDeviceAttributes deviceAttr =
(device != null) ? new AudioDeviceAttributes(device) : null;
- CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, pid, deviceAttr,
- device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true);
+ CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, uid, deviceAttr,
+ device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true, isPrivileged);
postSetCommunicationDeviceForClient(deviceInfo);
boolean status;
synchronized (deviceInfo) {
@@ -353,7 +356,7 @@
Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo);
}
if (!deviceInfo.mOn) {
- CommunicationRouteClient client = getCommunicationRouteClientForPid(deviceInfo.mPid);
+ CommunicationRouteClient client = getCommunicationRouteClientForUid(deviceInfo.mUid);
if (client == null || (deviceInfo.mDevice != null
&& !deviceInfo.mDevice.equals(client.getDevice()))) {
return false;
@@ -361,22 +364,23 @@
}
AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null;
- setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mPid, device,
- deviceInfo.mScoAudioMode, deviceInfo.mEventSource);
+ setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mUid, device,
+ deviceInfo.mScoAudioMode, deviceInfo.mIsPrivileged, deviceInfo.mEventSource);
return true;
}
@GuardedBy("mDeviceStateLock")
/*package*/ void setCommunicationRouteForClient(
- IBinder cb, int pid, AudioDeviceAttributes device,
- int scoAudioMode, String eventSource) {
+ IBinder cb, int uid, AudioDeviceAttributes device,
+ int scoAudioMode, boolean isPrivileged, String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "setCommunicationRouteForClient: device: " + device);
+ Log.v(TAG, "setCommunicationRouteForClient: device: " + device
+ + ", eventSource: " + eventSource);
}
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "setCommunicationRouteForClient for pid: " + pid
- + " device: " + device
+ "setCommunicationRouteForClient for uid: " + uid
+ + " device: " + device + " isPrivileged: " + isPrivileged
+ " from API: " + eventSource)).printLog(TAG));
final boolean wasBtScoRequested = isBluetoothScoRequested();
@@ -385,16 +389,18 @@
// Save previous client route in case of failure to start BT SCO audio
AudioDeviceAttributes prevClientDevice = null;
- client = getCommunicationRouteClientForPid(pid);
+ boolean prevPrivileged = false;
+ client = getCommunicationRouteClientForUid(uid);
if (client != null) {
prevClientDevice = client.getDevice();
+ prevPrivileged = client.isPrivileged();
}
if (device != null) {
- client = addCommunicationRouteClient(cb, pid, device);
+ client = addCommunicationRouteClient(cb, uid, device, isPrivileged);
if (client == null) {
- Log.w(TAG, "setCommunicationRouteForClient: could not add client for pid: "
- + pid + " and device: " + device);
+ Log.w(TAG, "setCommunicationRouteForClient: could not add client for uid: "
+ + uid + " and device: " + device);
}
} else {
client = removeCommunicationRouteClient(cb, true);
@@ -406,11 +412,11 @@
boolean isBtScoRequested = isBluetoothScoRequested();
if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) {
if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
- Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for pid: "
- + pid);
+ Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: "
+ + uid);
// clean up or restore previous client selection
if (prevClientDevice != null) {
- addCommunicationRouteClient(cb, pid, prevClientDevice);
+ addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
} else {
removeCommunicationRouteClient(cb, true);
}
@@ -447,11 +453,12 @@
@GuardedBy("mDeviceStateLock")
private CommunicationRouteClient topCommunicationRouteClient() {
for (CommunicationRouteClient crc : mCommunicationRouteClients) {
- if (crc.getPid() == mAudioModeOwner.mPid) {
+ if (crc.getUid() == mAudioModeOwner.mUid) {
return crc;
}
}
- if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0) {
+ if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0
+ && mCommunicationRouteClients.get(0).isActive()) {
return mCommunicationRouteClients.get(0);
}
return null;
@@ -491,14 +498,48 @@
};
/*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) {
+ return isValidCommunicationDeviceType(device.getType());
+ }
+
+ private static boolean isValidCommunicationDeviceType(int deviceType) {
for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
- if (device.getType() == type) {
+ if (deviceType == type) {
return true;
}
}
return false;
}
+ /*package */
+ void postCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) {
+ if (!isValidCommunicationDeviceType(
+ AudioDeviceInfo.convertInternalDeviceToDeviceType(device.getInternalType()))) {
+ return;
+ }
+ sendLMsgNoDelay(MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL, SENDMSG_QUEUE, device);
+ }
+
+ @GuardedBy("mDeviceStateLock")
+ void onCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) {
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "onCheckCommunicationDeviceRemoval device: " + device.toString());
+ }
+ for (CommunicationRouteClient crc : mCommunicationRouteClients) {
+ if (device.equals(crc.getDevice())) {
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "onCheckCommunicationDeviceRemoval removing client: "
+ + crc.toString());
+ }
+ // Cancelling the route for this client will remove it from the stack and update
+ // the communication route.
+ CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(
+ crc.getBinder(), crc.getUid(), device, false,
+ BtHelper.SCO_MODE_UNDEFINED, "onCheckCommunicationDeviceRemoval",
+ false, crc.isPrivileged());
+ postSetCommunicationDeviceForClient(deviceInfo);
+ }
+ }
+ }
/* package */ static List<AudioDeviceInfo> getAvailableCommunicationDevices() {
ArrayList<AudioDeviceInfo> commDevices = new ArrayList<>();
AudioDeviceInfo[] allDevices =
@@ -1107,26 +1148,26 @@
sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info);
}
- /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode,
- @NonNull String eventSource) {
+ /*package*/ void startBluetoothScoForClient(IBinder cb, int uid, int scoAudioMode,
+ boolean isPrivileged, @NonNull String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "startBluetoothScoForClient, pid: " + pid);
+ Log.v(TAG, "startBluetoothScoForClient, uid: " + uid);
}
postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
- cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
- true, scoAudioMode, eventSource, false));
+ cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+ true, scoAudioMode, eventSource, false, isPrivileged));
}
/*package*/ void stopBluetoothScoForClient(
- IBinder cb, int pid, @NonNull String eventSource) {
+ IBinder cb, int uid, boolean isPrivileged, @NonNull String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "stopBluetoothScoForClient, pid: " + pid);
+ Log.v(TAG, "stopBluetoothScoForClient, uid: " + uid);
}
postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
- cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
- false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
+ cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+ false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false, isPrivileged));
}
/*package*/ int setPreferredDevicesForStrategySync(int strategy,
@@ -1367,22 +1408,24 @@
/*package*/ static final class CommunicationDeviceInfo {
final @NonNull IBinder mCb; // Identifies the requesting client for death handler
- final int mPid; // Requester process ID
+ final int mUid; // Requester UID
final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset.
final boolean mOn; // true if setting, false if resetting
final int mScoAudioMode; // only used for SCO: requested audio mode
+ final boolean mIsPrivileged; // true if the client app has MODIFY_PHONE_STATE permission
final @NonNull String mEventSource; // caller identifier for logging
boolean mWaitForStatus; // true if the caller waits for a completion status (API dependent)
boolean mStatus = false; // completion status only used if mWaitForStatus is true
- CommunicationDeviceInfo(@NonNull IBinder cb, int pid,
+ CommunicationDeviceInfo(@NonNull IBinder cb, int uid,
@Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode,
- @NonNull String eventSource, boolean waitForStatus) {
+ @NonNull String eventSource, boolean waitForStatus, boolean isPrivileged) {
mCb = cb;
- mPid = pid;
+ mUid = uid;
mDevice = device;
mOn = on;
mScoAudioMode = scoAudioMode;
+ mIsPrivileged = isPrivileged;
mEventSource = eventSource;
mWaitForStatus = waitForStatus;
}
@@ -1401,16 +1444,17 @@
}
return mCb.equals(((CommunicationDeviceInfo) o).mCb)
- && mPid == ((CommunicationDeviceInfo) o).mPid;
+ && mUid == ((CommunicationDeviceInfo) o).mUid;
}
@Override
public String toString() {
return "CommunicationDeviceInfo mCb=" + mCb.toString()
- + " mPid=" + mPid
+ + " mUid=" + mUid
+ " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]"
+ " mOn=" + mOn
+ " mScoAudioMode=" + mScoAudioMode
+ + " mIsPrivileged=" + mIsPrivileged
+ " mEventSource=" + mEventSource
+ " mWaitForStatus=" + mWaitForStatus
+ " mStatus=" + mStatus;
@@ -1440,7 +1484,7 @@
}
}
- /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes,
+ /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
boolean connect, @Nullable BluetoothDevice btDevice) {
synchronized (mDeviceStateLock) {
return mDeviceInventory.handleDeviceConnection(
@@ -1507,8 +1551,7 @@
pw.println("\n" + prefix + "Communication route clients:");
mCommunicationRouteClients.forEach((cl) -> {
- pw.println(" " + prefix + "pid: " + cl.getPid() + " device: "
- + cl.getDevice() + " cb: " + cl.getBinder()); });
+ pw.println(" " + prefix + cl.toString()); });
pw.println("\n" + prefix + "Computed Preferred communication device: "
+ preferredCommunicationDevice());
@@ -1850,6 +1893,15 @@
final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
} break;
+
+ case MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL: {
+ synchronized (mSetModeLock) {
+ synchronized (mDeviceStateLock) {
+ onCheckCommunicationDeviceRemoval((AudioDeviceAttributes) msg.obj);
+ }
+ }
+ } break;
+
default:
Log.wtf(TAG, "Invalid message " + msg.what);
}
@@ -1926,6 +1978,7 @@
private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49;
private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
+ private static final int MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL = 53;
private static boolean isMessageHandledUnderWakelock(int msgId) {
switch(msgId) {
@@ -2101,13 +2154,20 @@
private class CommunicationRouteClient implements IBinder.DeathRecipient {
private final IBinder mCb;
- private final int mPid;
+ private final int mUid;
+ private final boolean mIsPrivileged;
private AudioDeviceAttributes mDevice;
+ private boolean mPlaybackActive;
+ private boolean mRecordingActive;
- CommunicationRouteClient(IBinder cb, int pid, AudioDeviceAttributes device) {
+ CommunicationRouteClient(IBinder cb, int uid, AudioDeviceAttributes device,
+ boolean isPrivileged) {
mCb = cb;
- mPid = pid;
+ mUid = uid;
mDevice = device;
+ mIsPrivileged = isPrivileged;
+ mPlaybackActive = mAudioService.isPlaybackActiveForUid(uid);
+ mRecordingActive = mAudioService.isRecordingActiveForUid(uid);
}
public boolean registerDeathRecipient() {
@@ -2138,13 +2198,38 @@
return mCb;
}
- int getPid() {
- return mPid;
+ int getUid() {
+ return mUid;
+ }
+
+ boolean isPrivileged() {
+ return mIsPrivileged;
}
AudioDeviceAttributes getDevice() {
return mDevice;
}
+
+ public void setPlaybackActive(boolean active) {
+ mPlaybackActive = active;
+ }
+
+ public void setRecordingActive(boolean active) {
+ mRecordingActive = active;
+ }
+
+ public boolean isActive() {
+ return mIsPrivileged || mRecordingActive || mPlaybackActive;
+ }
+
+ @Override
+ public String toString() {
+ return "[CommunicationRouteClient: mUid: " + mUid
+ + " mDevice: " + mDevice.toString()
+ + " mIsPrivileged: " + mIsPrivileged
+ + " mPlaybackActive: " + mPlaybackActive
+ + " mRecordingActive: " + mRecordingActive + "]";
+ }
}
// @GuardedBy("mSetModeLock")
@@ -2154,8 +2239,9 @@
return;
}
Log.w(TAG, "Communication client died");
- setCommunicationRouteForClient(client.getBinder(), client.getPid(), null,
- BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied");
+ setCommunicationRouteForClient(client.getBinder(), client.getUid(), null,
+ BtHelper.SCO_MODE_UNDEFINED, client.isPrivileged(),
+ "onCommunicationRouteClientDied");
}
/**
@@ -2242,8 +2328,8 @@
+ crc + " eventSource: " + eventSource);
}
if (crc != null) {
- setCommunicationRouteForClient(crc.getBinder(), crc.getPid(), crc.getDevice(),
- BtHelper.SCO_MODE_UNDEFINED, eventSource);
+ setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(),
+ BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
}
}
@@ -2267,6 +2353,7 @@
dispatchCommunicationDevice();
}
+ @GuardedBy("mDeviceStateLock")
private CommunicationRouteClient removeCommunicationRouteClient(
IBinder cb, boolean unregister) {
for (CommunicationRouteClient cl : mCommunicationRouteClients) {
@@ -2282,11 +2369,12 @@
}
@GuardedBy("mDeviceStateLock")
- private CommunicationRouteClient addCommunicationRouteClient(
- IBinder cb, int pid, AudioDeviceAttributes device) {
+ private CommunicationRouteClient addCommunicationRouteClient(IBinder cb, int uid,
+ AudioDeviceAttributes device, boolean isPrivileged) {
// always insert new request at first position
removeCommunicationRouteClient(cb, true);
- CommunicationRouteClient client = new CommunicationRouteClient(cb, pid, device);
+ CommunicationRouteClient client =
+ new CommunicationRouteClient(cb, uid, device, isPrivileged);
if (client.registerDeathRecipient()) {
mCommunicationRouteClients.add(0, client);
return client;
@@ -2295,9 +2383,9 @@
}
@GuardedBy("mDeviceStateLock")
- private CommunicationRouteClient getCommunicationRouteClientForPid(int pid) {
+ private CommunicationRouteClient getCommunicationRouteClientForUid(int uid) {
for (CommunicationRouteClient cl : mCommunicationRouteClients) {
- if (cl.getPid() == pid) {
+ if (cl.getUid() == uid) {
return cl;
}
}
@@ -2330,6 +2418,45 @@
return device;
}
+ void updateCommunicationRouteClientsActivity(
+ List<AudioPlaybackConfiguration> playbackConfigs,
+ List<AudioRecordingConfiguration> recordConfigs) {
+ synchronized (mSetModeLock) {
+ synchronized (mDeviceStateLock) {
+ boolean updateCommunicationRoute = false;
+ for (CommunicationRouteClient crc : mCommunicationRouteClients) {
+ boolean wasActive = crc.isActive();
+ if (playbackConfigs != null) {
+ crc.setPlaybackActive(false);
+ for (AudioPlaybackConfiguration config : playbackConfigs) {
+ if (config.getClientUid() == crc.getUid()
+ && config.isActive()) {
+ crc.setPlaybackActive(true);
+ break;
+ }
+ }
+ }
+ if (recordConfigs != null) {
+ crc.setRecordingActive(false);
+ for (AudioRecordingConfiguration config : recordConfigs) {
+ if (config.getClientUid() == crc.getUid()
+ && !config.isClientSilenced()) {
+ crc.setRecordingActive(true);
+ break;
+ }
+ }
+ }
+ if (wasActive != crc.isActive()) {
+ updateCommunicationRoute = true;
+ }
+ }
+ if (updateCommunicationRoute) {
+ postUpdateCommunicationRouteClient("updateCommunicationRouteClientsActivity");
+ }
+ }
+ }
+ }
+
@Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
synchronized (mDeviceStateLock) {
return mDeviceInventory.getDeviceSensorUuid(device);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 0c7f11f..b70e11d 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1245,8 +1245,9 @@
* @param btDevice the corresponding Bluetooth device when relevant.
* @return false if an error was reported by AudioSystem
*/
- /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect,
- boolean isForTesting, @Nullable BluetoothDevice btDevice) {
+ /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
+ boolean connect, boolean isForTesting,
+ @Nullable BluetoothDevice btDevice) {
int device = attributes.getInternalType();
String address = attributes.getAddress();
String deviceName = attributes.getName();
@@ -1297,6 +1298,7 @@
AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
// always remove even if disconnection failed
mConnectedDevices.remove(deviceKey);
+ mDeviceBroker.postCheckCommunicationDeviceRemoval(attributes);
status = true;
}
if (status) {
@@ -1801,8 +1803,9 @@
// device to remove was visible by APM, update APM
mDeviceBroker.clearAvrcpAbsoluteVolumeSupported();
- final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
+ AudioDeviceAttributes ada = new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+ final int res = mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec);
if (res != AudioSystem.AUDIO_STATUS_OK) {
@@ -1816,11 +1819,13 @@
"A2DP device addr=" + address + " made unavailable")).printLog(TAG));
}
mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+
// Remove A2DP routes as well
setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/);
mmi.record();
updateBluetoothPreferredModes_l(null /*connectedDevice*/);
purgeDevicesRoles_l();
+ mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
}
@GuardedBy("mDevicesLock")
@@ -1855,12 +1860,14 @@
@GuardedBy("mDevicesLock")
private void makeA2dpSrcUnavailable(String address) {
- mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
- AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
+ AudioDeviceAttributes ada = new AudioDeviceAttributes(
+ AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
+ mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_UNAVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.remove(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
+ mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
}
@GuardedBy("mDevicesLock")
@@ -1893,8 +1900,9 @@
@GuardedBy("mDevicesLock")
private void makeHearingAidDeviceUnavailable(String address) {
- mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
- AudioSystem.DEVICE_OUT_HEARING_AID, address),
+ AudioDeviceAttributes ada = new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_HEARING_AID, address);
+ mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_UNAVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.remove(
@@ -1906,6 +1914,7 @@
.set(MediaMetrics.Property.DEVICE,
AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID))
.record();
+ mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
}
/**
@@ -2002,9 +2011,10 @@
@GuardedBy("mDevicesLock")
private void makeLeAudioDeviceUnavailableNow(String address, int device) {
+ AudioDeviceAttributes ada = null;
if (device != AudioSystem.DEVICE_NONE) {
- final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
- device, address),
+ ada = new AudioDeviceAttributes(device, address);
+ final int res = AudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_UNAVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
@@ -2024,6 +2034,9 @@
setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
updateBluetoothPreferredModes_l(null /*connectedDevice*/);
purgeDevicesRoles_l();
+ if (ada != null) {
+ mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
+ }
}
@GuardedBy("mDevicesLock")
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 53ed38e..b70b2b3 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4262,22 +4262,41 @@
// When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
// and request an audio mode update immediately. Upon any other change, queue the message
// and request an audio mode update after a grace period.
+ updateAudioModeHandlers(
+ configs /* playbackConfigs */, null /* recordConfigs */);
+ mDeviceBroker.updateCommunicationRouteClientsActivity(
+ configs /* playbackConfigs */, null /* recordConfigs */);
+ }
+
+ void updateAudioModeHandlers(List<AudioPlaybackConfiguration> playbackConfigs,
+ List<AudioRecordingConfiguration> recordConfigs) {
synchronized (mDeviceBroker.mSetModeLock) {
boolean updateAudioMode = false;
int existingMsgPolicy = SENDMSG_QUEUE;
int delay = CHECK_MODE_FOR_UID_PERIOD_MS;
for (SetModeDeathHandler h : mSetModeDeathHandlers) {
boolean wasActive = h.isActive();
- h.setPlaybackActive(false);
- for (AudioPlaybackConfiguration config : configs) {
- final int usage = config.getAudioAttributes().getUsage();
- if (config.getClientUid() == h.getUid()
- && (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
+ if (playbackConfigs != null) {
+ h.setPlaybackActive(false);
+ for (AudioPlaybackConfiguration config : playbackConfigs) {
+ final int usage = config.getAudioAttributes().getUsage();
+ if (config.getClientUid() == h.getUid()
+ && (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
|| usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
- && config.getPlayerState()
- == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
- h.setPlaybackActive(true);
- break;
+ && config.isActive()) {
+ h.setPlaybackActive(true);
+ break;
+ }
+ }
+ }
+ if (recordConfigs != null) {
+ h.setRecordingActive(false);
+ for (AudioRecordingConfiguration config : recordConfigs) {
+ if (config.getClientUid() == h.getUid() && !config.isClientSilenced()
+ && config.getAudioSource() == AudioSource.VOICE_COMMUNICATION) {
+ h.setRecordingActive(true);
+ break;
+ }
}
}
if (wasActive != h.isActive()) {
@@ -4315,38 +4334,10 @@
// When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
// and request an audio mode update immediately. Upon any other change, queue the message
// and request an audio mode update after a grace period.
- synchronized (mDeviceBroker.mSetModeLock) {
- boolean updateAudioMode = false;
- int existingMsgPolicy = SENDMSG_QUEUE;
- int delay = CHECK_MODE_FOR_UID_PERIOD_MS;
- for (SetModeDeathHandler h : mSetModeDeathHandlers) {
- boolean wasActive = h.isActive();
- h.setRecordingActive(false);
- for (AudioRecordingConfiguration config : configs) {
- if (config.getClientUid() == h.getUid()
- && config.getAudioSource() == AudioSource.VOICE_COMMUNICATION) {
- h.setRecordingActive(true);
- break;
- }
- }
- if (wasActive != h.isActive()) {
- updateAudioMode = true;
- if (h.isActive() && h == getAudioModeOwnerHandler()) {
- existingMsgPolicy = SENDMSG_REPLACE;
- delay = 0;
- }
- }
- }
- if (updateAudioMode) {
- sendMsg(mAudioHandler,
- MSG_UPDATE_AUDIO_MODE,
- existingMsgPolicy,
- AudioSystem.MODE_CURRENT,
- android.os.Process.myPid(),
- mContext.getPackageName(),
- delay);
- }
- }
+ updateAudioModeHandlers(
+ null /* playbackConfigs */, configs /* recordConfigs */);
+ mDeviceBroker.updateCommunicationRouteClientsActivity(
+ null /* playbackConfigs */, configs /* recordConfigs */);
}
private void dumpAudioMode(PrintWriter pw) {
@@ -6299,10 +6290,12 @@
? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
.record();
}
-
+ final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_PHONE_STATE)
+ == PackageManager.PERMISSION_GRANTED;
final long ident = Binder.clearCallingIdentity();
try {
- return mDeviceBroker.setCommunicationDevice(cb, pid, device, eventSource);
+ return mDeviceBroker.setCommunicationDevice(cb, uid, device, isPrivileged, eventSource);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -6348,6 +6341,9 @@
if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) {
return;
}
+ final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_PHONE_STATE)
+ == PackageManager.PERMISSION_GRANTED;
// for logging only
final int uid = Binder.getCallingUid();
@@ -6363,9 +6359,10 @@
.set(MediaMetrics.Property.STATE, on
? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
.record();
+
final long ident = Binder.clearCallingIdentity();
try {
- mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource);
+ mDeviceBroker.setSpeakerphoneOn(cb, uid, on, isPrivileged, eventSource);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -6490,7 +6487,7 @@
.set(MediaMetrics.Property.SCO_AUDIO_MODE,
BtHelper.scoAudioModeToString(scoAudioMode))
.record();
- startBluetoothScoInt(cb, pid, scoAudioMode, eventSource);
+ startBluetoothScoInt(cb, uid, scoAudioMode, eventSource);
}
@@ -6513,10 +6510,10 @@
.set(MediaMetrics.Property.SCO_AUDIO_MODE,
BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL))
.record();
- startBluetoothScoInt(cb, pid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
+ startBluetoothScoInt(cb, uid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
}
- void startBluetoothScoInt(IBinder cb, int pid, int scoAudioMode, @NonNull String eventSource) {
+ void startBluetoothScoInt(IBinder cb, int uid, int scoAudioMode, @NonNull String eventSource) {
MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
.set(MediaMetrics.Property.EVENT, "startBluetoothScoInt")
.set(MediaMetrics.Property.SCO_AUDIO_MODE,
@@ -6527,9 +6524,13 @@
mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record();
return;
}
+ final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_PHONE_STATE)
+ == PackageManager.PERMISSION_GRANTED;
final long ident = Binder.clearCallingIdentity();
try {
- mDeviceBroker.startBluetoothScoForClient(cb, pid, scoAudioMode, eventSource);
+ mDeviceBroker.startBluetoothScoForClient(
+ cb, uid, scoAudioMode, isPrivileged, eventSource);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -6547,9 +6548,12 @@
final String eventSource = new StringBuilder("stopBluetoothSco()")
.append(") from u/pid:").append(uid).append("/")
.append(pid).toString();
+ final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_PHONE_STATE)
+ == PackageManager.PERMISSION_GRANTED;
final long ident = Binder.clearCallingIdentity();
try {
- mDeviceBroker.stopBluetoothScoForClient(cb, pid, eventSource);
+ mDeviceBroker.stopBluetoothScoForClient(cb, uid, isPrivileged, eventSource);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -9284,8 +9288,8 @@
break;
}
boolean wasActive = h.isActive();
- h.setPlaybackActive(mPlaybackMonitor.isPlaybackActiveForUid(h.getUid()));
- h.setRecordingActive(mRecordMonitor.isRecordingActiveForUid(h.getUid()));
+ h.setPlaybackActive(isPlaybackActiveForUid(h.getUid()));
+ h.setRecordingActive(isRecordingActiveForUid(h.getUid()));
if (wasActive != h.isActive()) {
onUpdateAudioMode(AudioSystem.MODE_CURRENT, android.os.Process.myPid(),
mContext.getPackageName(), false /*force*/);
@@ -12378,6 +12382,16 @@
}
}
+ /* package */
+ boolean isPlaybackActiveForUid(int uid) {
+ return mPlaybackMonitor.isPlaybackActiveForUid(uid);
+ }
+
+ /* package */
+ boolean isRecordingActiveForUid(int uid) {
+ return mRecordMonitor.isRecordingActiveForUid(uid);
+ }
+
//======================
// Audio device management
//======================
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 652ea52..4332fdd 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -227,8 +227,8 @@
synchronized (mRecordStates) {
for (RecordingState state : mRecordStates) {
// Note: isActiveConfiguration() == true => state.getConfig() != null
- if (state.isActiveConfiguration()
- && state.getConfig().getClientUid() == uid) {
+ if (state.isActiveConfiguration() && state.getConfig().getClientUid() == uid
+ && !state.getConfig().isClientSilenced()) {
return true;
}
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index e85eee81..e3262cf 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -22,7 +22,6 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.RouteInfo.RTN_THROW;
import static android.net.RouteInfo.RTN_UNREACHABLE;
import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
@@ -280,15 +279,22 @@
private static final int VPN_DEFAULT_SCORE = 101;
/**
- * The reset session timer for data stall. If a session has not successfully revalidated after
- * the delay, the session will be torn down and restarted in an attempt to recover. Delay
+ * The recovery timer for data stall. If a session has not successfully revalidated after
+ * the delay, the session will perform MOBIKE or be restarted in an attempt to recover. Delay
* counter is reset on successful validation only.
*
+ * <p>The first {@code MOBIKE_RECOVERY_ATTEMPT} timers are used for performing MOBIKE.
+ * System will perform session reset for the remaining timers.
* <p>If retries have exceeded the length of this array, the last entry in the array will be
* used as a repeating interval.
*/
- private static final long[] DATA_STALL_RESET_DELAYS_SEC = {30L, 60L, 120L, 240L, 480L, 960L};
-
+ // TODO: use ms instead to speed up the test.
+ private static final long[] DATA_STALL_RECOVERY_DELAYS_SEC =
+ {1L, 5L, 30L, 60L, 120L, 240L, 480L, 960L};
+ /**
+ * Maximum attempts to perform MOBIKE when the network is bad.
+ */
+ private static final int MAX_MOBIKE_RECOVERY_ATTEMPT = 2;
/**
* The initial token value of IKE session.
*/
@@ -380,6 +386,7 @@
private final INetworkManagementService mNms;
private final INetd mNetd;
@VisibleForTesting
+ @GuardedBy("this")
protected VpnConfig mConfig;
private final NetworkProvider mNetworkProvider;
@VisibleForTesting
@@ -392,7 +399,6 @@
private final UserManager mUserManager;
private final VpnProfileStore mVpnProfileStore;
- protected boolean mDataStallSuspected = false;
@VisibleForTesting
VpnProfileStore getVpnProfileStore() {
@@ -685,14 +691,14 @@
}
/**
- * Get the length of time to wait before resetting the ike session when a data stall is
- * suspected.
+ * Get the length of time to wait before perform data stall recovery when the validation
+ * result is bad.
*/
- public long getDataStallResetSessionSeconds(int count) {
- if (count >= DATA_STALL_RESET_DELAYS_SEC.length) {
- return DATA_STALL_RESET_DELAYS_SEC[DATA_STALL_RESET_DELAYS_SEC.length - 1];
+ public long getValidationFailRecoverySeconds(int count) {
+ if (count >= DATA_STALL_RECOVERY_DELAYS_SEC.length) {
+ return DATA_STALL_RECOVERY_DELAYS_SEC[DATA_STALL_RECOVERY_DELAYS_SEC.length - 1];
} else {
- return DATA_STALL_RESET_DELAYS_SEC[count];
+ return DATA_STALL_RECOVERY_DELAYS_SEC[count];
}
}
@@ -1598,6 +1604,8 @@
return network;
}
+ // TODO : this is not synchronized(this) but reads from mConfig, which is dangerous
+ // This file makes an effort to avoid partly initializing mConfig, but this is still not great
private LinkProperties makeLinkProperties() {
// The design of disabling IPv6 is only enabled for IKEv2 VPN because it needs additional
// logic to handle IPv6 only VPN, and the IPv6 only VPN may be restarted when its MTU
@@ -1679,6 +1687,7 @@
* registering a new NetworkAgent. This is not always possible if the new VPN configuration
* has certain changes, in which case this method would just return {@code false}.
*/
+ // TODO : this method is not synchronized(this) but reads from mConfig
private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) {
// NetworkAgentConfig cannot be updated without registering a new NetworkAgent.
// Strictly speaking, bypassability is affected by lockdown and therefore it's possible
@@ -2269,7 +2278,12 @@
*/
public synchronized VpnConfig getVpnConfig() {
enforceControlPermission();
- return mConfig;
+ // Constructor of VpnConfig cannot take a null parameter. Return null directly if mConfig is
+ // null
+ if (mConfig == null) return null;
+ // mConfig is guarded by "this" and can be modified by another thread as soon as
+ // this method returns, so this method must return a copy.
+ return new VpnConfig(mConfig);
}
@Deprecated
@@ -2315,6 +2329,7 @@
}
};
+ @GuardedBy("this")
private void cleanupVpnStateLocked() {
mStatusIntent = null;
resetNetworkCapabilities();
@@ -2837,9 +2852,7 @@
}
final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner;
-
mVpnRunner.exit();
- mVpnRunner = null;
// LegacyVpn uses daemons that must be shut down before new ones are brought up.
// The same limitation does not apply to Platform VPNs.
@@ -3044,7 +3057,6 @@
@Nullable private IkeSessionWrapper mSession;
@Nullable private IkeSessionConnectionInfo mIkeConnectionInfo;
- @Nullable private VpnConnectivityDiagnosticsCallback mDiagnosticsCallback;
// mMobikeEnabled can only be updated after IKE AUTH is finished.
private boolean mMobikeEnabled = false;
@@ -3055,7 +3067,7 @@
* <p>This variable controls the retry delay, and is reset when the VPN pass network
* validation.
*/
- private int mDataStallRetryCount = 0;
+ private int mValidationFailRetryCount = 0;
/**
* The number of attempts since the last successful connection.
@@ -3084,6 +3096,7 @@
}
};
+ // GuardedBy("Vpn.this") (annotation can't be applied to constructor)
IkeV2VpnRunner(
@NonNull Ikev2VpnProfile profile, @NonNull ScheduledThreadPoolExecutor executor) {
super(TAG);
@@ -3136,15 +3149,6 @@
mConnectivityManager.registerSystemDefaultNetworkCallback(mNetworkCallback,
new Handler(mLooper));
}
-
- // DiagnosticsCallback may return more than one alive VPNs, but VPN will filter based on
- // Network object.
- final NetworkRequest diagRequest = new NetworkRequest.Builder()
- .addTransportType(TRANSPORT_VPN)
- .removeCapability(NET_CAPABILITY_NOT_VPN).build();
- mDiagnosticsCallback = new VpnConnectivityDiagnosticsCallback();
- mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(
- diagRequest, mExecutor, mDiagnosticsCallback);
}
private boolean isActiveNetwork(@Nullable Network network) {
@@ -3710,11 +3714,14 @@
}
public void updateVpnTransportInfoAndNetCap(int keepaliveDelaySec) {
- final VpnTransportInfo info = new VpnTransportInfo(
- getActiveVpnType(),
- mConfig.session,
- mConfig.allowBypass && !mLockdown,
- areLongLivedTcpConnectionsExpensive(keepaliveDelaySec));
+ final VpnTransportInfo info;
+ synchronized (Vpn.this) {
+ info = new VpnTransportInfo(
+ getActiveVpnType(),
+ mConfig.session,
+ mConfig.allowBypass && !mLockdown,
+ areLongLivedTcpConnectionsExpensive(keepaliveDelaySec));
+ }
final boolean ncUpdateRequired = !info.equals(mNetworkCapabilities.getTransportInfo());
if (ncUpdateRequired) {
mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
@@ -3875,39 +3882,12 @@
}
}
- class VpnConnectivityDiagnosticsCallback
- extends ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback {
- // The callback runs in the executor thread.
- @Override
- public void onDataStallSuspected(
- ConnectivityDiagnosticsManager.DataStallReport report) {
- synchronized (Vpn.this) {
- // Ignore stale runner.
- if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return;
-
- // Handle the report only for current VPN network. If data stall is already
- // reported, ignoring the other reports. It means that the stall is not
- // recovered by MOBIKE and should be on the way to reset the ike session.
- if (mNetworkAgent != null
- && mNetworkAgent.getNetwork().equals(report.getNetwork())
- && !mDataStallSuspected) {
- Log.d(TAG, "Data stall suspected");
-
- // Trigger MOBIKE.
- maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork);
- mDataStallSuspected = true;
- }
- }
- }
- }
-
public void onValidationStatus(int status) {
mEventChanges.log("[Validation] validation status " + status);
if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
// No data stall now. Reset it.
mExecutor.execute(() -> {
- mDataStallSuspected = false;
- mDataStallRetryCount = 0;
+ mValidationFailRetryCount = 0;
if (mScheduledHandleDataStallFuture != null) {
Log.d(TAG, "Recovered from stall. Cancel pending reset action.");
mScheduledHandleDataStallFuture.cancel(false /* mayInterruptIfRunning */);
@@ -3918,8 +3898,21 @@
// Skip other invalid status if the scheduled recovery exists.
if (mScheduledHandleDataStallFuture != null) return;
+ if (mValidationFailRetryCount < MAX_MOBIKE_RECOVERY_ATTEMPT) {
+ Log.d(TAG, "Validation failed");
+
+ // Trigger MOBIKE to recover first.
+ mExecutor.schedule(() -> {
+ maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork);
+ }, mDeps.getValidationFailRecoverySeconds(mValidationFailRetryCount++),
+ TimeUnit.SECONDS);
+ return;
+ }
+
+ // Data stall is not recovered by MOBIKE. Try to reset session to recover it.
mScheduledHandleDataStallFuture = mExecutor.schedule(() -> {
- if (mDataStallSuspected) {
+ // Only perform the recovery when the network is still bad.
+ if (mValidationFailRetryCount > 0) {
Log.d(TAG, "Reset session to recover stalled network");
// This will reset old state if it exists.
startIkeSession(mActiveNetwork);
@@ -3928,7 +3921,9 @@
// Reset mScheduledHandleDataStallFuture since it's already run on executor
// thread.
mScheduledHandleDataStallFuture = null;
- }, mDeps.getDataStallResetSessionSeconds(mDataStallRetryCount++), TimeUnit.SECONDS);
+ // TODO: compute the delay based on the last recovery timestamp
+ }, mDeps.getValidationFailRecoverySeconds(mValidationFailRetryCount++),
+ TimeUnit.SECONDS);
}
}
@@ -4220,7 +4215,7 @@
* consistency of the Ikev2VpnRunner fields.
*/
private void disconnectVpnRunner() {
- mEventChanges.log("[VPNRunner] Disconnect runner, underlying network" + mActiveNetwork);
+ mEventChanges.log("[VPNRunner] Disconnect runner, underlying net " + mActiveNetwork);
mActiveNetwork = null;
mUnderlyingNetworkCapabilities = null;
mUnderlyingLinkProperties = null;
@@ -4231,8 +4226,6 @@
mCarrierConfigManager.unregisterCarrierConfigChangeListener(
mCarrierConfigChangeListener);
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
- mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
- mDiagnosticsCallback);
clearVpnNetworkPreference(mSessionKey);
mExecutor.shutdown();
@@ -4293,6 +4286,7 @@
}
};
+ // GuardedBy("Vpn.this") (annotation can't be applied to constructor)
LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) {
super(TAG);
if (racoon == null && mtpd == null) {
@@ -4500,46 +4494,46 @@
}
// Set the interface and the addresses in the config.
- mConfig.interfaze = parameters[0].trim();
-
- mConfig.addLegacyAddresses(parameters[1]);
- // Set the routes if they are not set in the config.
- if (mConfig.routes == null || mConfig.routes.isEmpty()) {
- mConfig.addLegacyRoutes(parameters[2]);
- }
-
- // Set the DNS servers if they are not set in the config.
- if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
- String dnsServers = parameters[3].trim();
- if (!dnsServers.isEmpty()) {
- mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
- }
- }
-
- // Set the search domains if they are not set in the config.
- if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
- String searchDomains = parameters[4].trim();
- if (!searchDomains.isEmpty()) {
- mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
- }
- }
-
- // Add a throw route for the VPN server endpoint, if one was specified.
- if (endpointAddress instanceof Inet4Address) {
- mConfig.routes.add(new RouteInfo(
- new IpPrefix(endpointAddress, 32), null /*gateway*/,
- null /*iface*/, RTN_THROW));
- } else if (endpointAddress instanceof Inet6Address) {
- mConfig.routes.add(new RouteInfo(
- new IpPrefix(endpointAddress, 128), null /*gateway*/,
- null /*iface*/, RTN_THROW));
- } else {
- Log.e(TAG, "Unknown IP address family for VPN endpoint: "
- + endpointAddress);
- }
-
- // Here is the last step and it must be done synchronously.
synchronized (Vpn.this) {
+ mConfig.interfaze = parameters[0].trim();
+
+ mConfig.addLegacyAddresses(parameters[1]);
+ // Set the routes if they are not set in the config.
+ if (mConfig.routes == null || mConfig.routes.isEmpty()) {
+ mConfig.addLegacyRoutes(parameters[2]);
+ }
+
+ // Set the DNS servers if they are not set in the config.
+ if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
+ String dnsServers = parameters[3].trim();
+ if (!dnsServers.isEmpty()) {
+ mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
+ }
+ }
+
+ // Set the search domains if they are not set in the config.
+ if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
+ String searchDomains = parameters[4].trim();
+ if (!searchDomains.isEmpty()) {
+ mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
+ }
+ }
+
+ // Add a throw route for the VPN server endpoint, if one was specified.
+ if (endpointAddress instanceof Inet4Address) {
+ mConfig.routes.add(new RouteInfo(
+ new IpPrefix(endpointAddress, 32), null /*gateway*/,
+ null /*iface*/, RTN_THROW));
+ } else if (endpointAddress instanceof Inet6Address) {
+ mConfig.routes.add(new RouteInfo(
+ new IpPrefix(endpointAddress, 128), null /*gateway*/,
+ null /*iface*/, RTN_THROW));
+ } else {
+ Log.e(TAG, "Unknown IP address family for VPN endpoint: "
+ + endpointAddress);
+ }
+
+ // Here is the last step and it must be done synchronously.
// Set the start time
mConfig.startTime = SystemClock.elapsedRealtime();
@@ -4773,25 +4767,26 @@
try {
// Build basic config
- mConfig = new VpnConfig();
+ final VpnConfig config = new VpnConfig();
if (VpnConfig.LEGACY_VPN.equals(packageName)) {
- mConfig.legacy = true;
- mConfig.session = profile.name;
- mConfig.user = profile.key;
+ config.legacy = true;
+ config.session = profile.name;
+ config.user = profile.key;
// TODO: Add support for configuring meteredness via Settings. Until then, use a
// safe default.
- mConfig.isMetered = true;
+ config.isMetered = true;
} else {
- mConfig.user = packageName;
- mConfig.isMetered = profile.isMetered;
+ config.user = packageName;
+ config.isMetered = profile.isMetered;
}
- mConfig.startTime = SystemClock.elapsedRealtime();
- mConfig.proxyInfo = profile.proxy;
- mConfig.requiresInternetValidation = profile.requiresInternetValidation;
- mConfig.excludeLocalRoutes = profile.excludeLocalRoutes;
- mConfig.allowBypass = profile.isBypassable;
- mConfig.disallowedApplications = getAppExclusionList(mPackage);
+ config.startTime = SystemClock.elapsedRealtime();
+ config.proxyInfo = profile.proxy;
+ config.requiresInternetValidation = profile.requiresInternetValidation;
+ config.excludeLocalRoutes = profile.excludeLocalRoutes;
+ config.allowBypass = profile.isBypassable;
+ config.disallowedApplications = getAppExclusionList(mPackage);
+ mConfig = config;
switch (profile.type) {
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
@@ -4805,6 +4800,7 @@
mVpnRunner.start();
break;
default:
+ mConfig = null;
updateState(DetailedState.FAILED, "Invalid platform VPN type");
Log.d(TAG, "Unknown VPN profile type: " + profile.type);
break;
@@ -5216,7 +5212,7 @@
pw.println("MOBIKE " + (runner.mMobikeEnabled ? "enabled" : "disabled"));
pw.println("Profile: " + runner.mProfile);
pw.println("Token: " + runner.mCurrentToken);
- if (mDataStallSuspected) pw.println("Data stall suspected");
+ pw.println("Validation failed retry count:" + runner.mValidationFailRetryCount);
if (runner.mScheduledHandleDataStallFuture != null) {
pw.println("Reset session scheduled");
}
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 47cde15..5b11cfe 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -18,29 +18,42 @@
import android.hardware.display.BrightnessInfo;
import android.os.IBinder;
+import android.provider.DeviceConfigInterface;
+
+import com.android.server.display.feature.DeviceConfigParameterProvider;
import java.io.PrintWriter;
import java.util.function.BooleanSupplier;
class BrightnessRangeController {
- private static final boolean NBM_FEATURE_FLAG = false;
-
private final HighBrightnessModeController mHbmController;
private final NormalBrightnessModeController mNormalBrightnessModeController =
new NormalBrightnessModeController();
private final Runnable mModeChangeCallback;
+ private final boolean mUseNbmController;
+
BrightnessRangeController(HighBrightnessModeController hbmController,
Runnable modeChangeCallback) {
- mHbmController = hbmController;
- mModeChangeCallback = modeChangeCallback;
+ this(hbmController, modeChangeCallback,
+ new DeviceConfigParameterProvider(DeviceConfigInterface.REAL));
}
+ BrightnessRangeController(HighBrightnessModeController hbmController,
+ Runnable modeChangeCallback, DeviceConfigParameterProvider configParameterProvider) {
+ mHbmController = hbmController;
+ mModeChangeCallback = modeChangeCallback;
+ mUseNbmController = configParameterProvider.isNormalBrightnessControllerFeatureEnabled();
+ }
void dump(PrintWriter pw) {
+ pw.println("BrightnessRangeController:");
+ pw.println(" mUseNormalBrightnessController=" + mUseNbmController);
mHbmController.dump(pw);
+ mNormalBrightnessModeController.dump(pw);
+
}
void onAmbientLuxChange(float ambientLux) {
@@ -90,7 +103,7 @@
float getCurrentBrightnessMax() {
- if (NBM_FEATURE_FLAG && mHbmController.getHighBrightnessMode()
+ if (mUseNbmController && mHbmController.getHighBrightnessMode()
== BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) {
return Math.min(mHbmController.getCurrentBrightnessMax(),
mNormalBrightnessModeController.getCurrentBrightnessMax());
@@ -111,7 +124,7 @@
}
private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) {
- if (NBM_FEATURE_FLAG) {
+ if (mUseNbmController) {
boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean();
hbmChangesFunc.run();
// if nbm transition changed - trigger callback
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
index cfdcd63..c421ec0 100644
--- a/services/core/java/com/android/server/display/BrightnessThrottler.java
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -22,7 +22,6 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.display.BrightnessInfo;
-import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IThermalEventListener;
@@ -38,6 +37,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -63,7 +63,7 @@
private final Runnable mThrottlingChangeCallback;
private final SkinThermalStatusObserver mSkinThermalStatusObserver;
private final DeviceConfigListener mDeviceConfigListener;
- private final DeviceConfigInterface mDeviceConfig;
+ private final DeviceConfigParameterProvider mConfigParameterProvider;
private int mThrottlingStatus;
@@ -118,7 +118,7 @@
mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
mUniqueDisplayId = uniqueDisplayId;
- mDeviceConfig = injector.getDeviceConfig();
+ mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
mDeviceConfigListener = new DeviceConfigListener();
mThermalBrightnessThrottlingDataId = throttlingDataId;
mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap;
@@ -145,7 +145,7 @@
void stop() {
mSkinThermalStatusObserver.stopObserving();
- mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener);
+ mConfigParameterProvider.removeOnPropertiesChangedListener(mDeviceConfigListener);
// We're asked to stop throttling, so reset brightness restrictions.
mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
@@ -248,12 +248,6 @@
mSkinThermalStatusObserver.dump(pw);
}
- private String getThermalBrightnessThrottlingDataString() {
- return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA,
- /* defaultValue= */ null);
- }
-
// The brightness throttling data id may or may not be specified in the string that is passed
// in, if there is none specified, we assume it is for the default case. Each string passed in
// here must be for one display and one throttling id.
@@ -318,7 +312,8 @@
private void loadThermalBrightnessThrottlingDataFromDeviceConfig() {
HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
new HashMap<>(1);
- mThermalBrightnessThrottlingDataString = getThermalBrightnessThrottlingDataString();
+ mThermalBrightnessThrottlingDataString =
+ mConfigParameterProvider.getBrightnessThrottlingData();
boolean validConfig = true;
mThermalBrightnessThrottlingDataOverride.clear();
if (mThermalBrightnessThrottlingDataString != null) {
@@ -390,8 +385,7 @@
public Executor mExecutor = new HandlerExecutor(mDeviceConfigHandler);
public void startListening() {
- mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- mExecutor, this);
+ mConfigParameterProvider.addOnPropertiesChangedListener(mExecutor, this);
}
@Override
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index f84a58c..472c1f5 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -22,7 +22,6 @@
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
-import android.view.Display;
import android.view.DisplayAddress;
import com.android.internal.annotations.VisibleForTesting;
@@ -38,6 +37,7 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.math.BigInteger;
import javax.xml.datatype.DatatypeConfigurationException;
@@ -115,13 +115,16 @@
Slog.i(TAG, "Display layout config not found: " + configFile);
return;
}
- int leadDisplayId = Display.DEFAULT_DISPLAY;
for (com.android.server.display.config.layout.Layout l : layouts.getLayout()) {
final int state = l.getState().intValue();
final Layout layout = createLayout(state);
for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
assert layout != null;
int position = getPosition(d.getPosition());
+ BigInteger leadDisplayPhysicalId = d.getLeadDisplayAddress();
+ DisplayAddress leadDisplayAddress = leadDisplayPhysicalId == null ? null
+ : DisplayAddress.fromPhysicalDisplayId(
+ leadDisplayPhysicalId.longValue());
layout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
d.isDefaultDisplay(),
@@ -129,11 +132,12 @@
d.getDisplayGroup(),
mIdProducer,
position,
- leadDisplayId,
+ leadDisplayAddress,
d.getBrightnessThrottlingMapId(),
d.getRefreshRateZoneId(),
d.getRefreshRateThermalThrottlingMapId());
}
+ layout.postProcessLocked();
}
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
Slog.e(TAG, "Encountered an error while reading/parsing display layout config file: "
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 858800a..dbe15b6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -24,7 +24,6 @@
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
import static android.hardware.display.DisplayManager.EventsMask;
-import static android.hardware.display.DisplayManager.HDR_OUTPUT_CONTROL_FLAG;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
@@ -42,7 +41,6 @@
import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPORTED;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.ROOT_UID;
-import static android.provider.DeviceConfig.NAMESPACE_DISPLAY_MANAGER;
import android.Manifest;
import android.annotation.NonNull;
@@ -116,7 +114,7 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -152,6 +150,7 @@
import com.android.server.UiThread;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.display.DisplayDeviceConfig.SensorData;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.layout.Layout;
import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.display.utils.SensorUtils;
@@ -506,6 +505,8 @@
private final BrightnessSynchronizer mBrightnessSynchronizer;
+ private final DeviceConfigParameterProvider mConfigParameterProvider;
+
/**
* Applications use {@link android.view.Display#getRefreshRate} and
* {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate.
@@ -558,6 +559,7 @@
mWideColorSpace = colorSpaces[1];
mOverlayProperties = SurfaceControl.getOverlaySupport();
mSystemReady = false;
+ mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
}
public void setupSchedulerPolicies() {
@@ -694,11 +696,11 @@
synchronized (mSyncRoot) {
mSafeMode = safeMode;
mSystemReady = true;
- mIsHdrOutputControlEnabled = isDeviceConfigHdrOutputControlEnabled();
- DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_DISPLAY_MANAGER,
- BackgroundThread.getExecutor(),
+ mIsHdrOutputControlEnabled =
+ mConfigParameterProvider.isHdrOutputControlFeatureEnabled();
+ mConfigParameterProvider.addOnPropertiesChangedListener(BackgroundThread.getExecutor(),
properties -> mIsHdrOutputControlEnabled =
- isDeviceConfigHdrOutputControlEnabled());
+ mConfigParameterProvider.isHdrOutputControlFeatureEnabled());
// Just in case the top inset changed before the system was ready. At this point, any
// relevant configuration should be in place.
recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY));
@@ -729,12 +731,6 @@
mContext.registerReceiver(mIdleModeReceiver, filter);
}
- private boolean isDeviceConfigHdrOutputControlEnabled() {
- return DeviceConfig.getBoolean(NAMESPACE_DISPLAY_MANAGER,
- HDR_OUTPUT_CONTROL_FLAG,
- true);
- }
-
@VisibleForTesting
Handler getDisplayHandler() {
return mHandler;
@@ -3158,8 +3154,7 @@
+ "display: " + display.getDisplayIdLocked());
return null;
}
- if (DeviceConfig.getBoolean("display_manager",
- "use_newly_structured_display_power_controller", true)) {
+ if (mConfigParameterProvider.isNewPowerControllerFeatureEnabled()) {
displayPowerController = new DisplayPowerController2(
mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 7701bc6..89d865e 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -496,7 +496,7 @@
private void loadDisplayDeviceConfig() {
// Load display device config
final Context context = getOverlayContext();
- mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId,
+ mDisplayDeviceConfig = mInjector.createDisplayDeviceConfig(context, mPhysicalDisplayId,
mIsFirstDisplay);
// Load brightness HWC quirk
@@ -1336,6 +1336,11 @@
public SurfaceControlProxy getSurfaceControlProxy() {
return new SurfaceControlProxy();
}
+
+ public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
+ long physicalDisplayId, boolean isFirstDisplay) {
+ return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay);
+ }
}
public interface DisplayEventListener {
diff --git a/services/core/java/com/android/server/display/NormalBrightnessModeController.java b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
index dbabc24..135ebd8 100644
--- a/services/core/java/com/android/server/display/NormalBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
@@ -21,6 +21,7 @@
import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType;
+import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
@@ -60,6 +61,14 @@
return recalculateMaxBrightness();
}
+ void dump(PrintWriter pw) {
+ pw.println("NormalBrightnessModeController:");
+ pw.println(" mAutoBrightnessEnabled=" + mAutoBrightnessEnabled);
+ pw.println(" mAmbientLux=" + mAmbientLux);
+ pw.println(" mMaxBrightness=" + mMaxBrightness);
+ pw.println(" mMaxBrightnessLimits=" + mMaxBrightnessLimits);
+ }
+
private boolean recalculateMaxBrightness() {
float foundAmbientBoundary = Float.MAX_VALUE;
float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
new file mode 100644
index 0000000..feebdf1
--- /dev/null
+++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 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.display.feature;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.display.DisplayManager;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
+import android.util.Slog;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class to access all DeviceConfig features for display_manager namespace
+ *
+ **/
+public class DeviceConfigParameterProvider {
+
+ private static final String TAG = "DisplayFeatureProvider";
+
+ private final DeviceConfigInterface mDeviceConfig;
+
+ public DeviceConfigParameterProvider(DeviceConfigInterface deviceConfig) {
+ mDeviceConfig = deviceConfig;
+ }
+
+ // feature: revamping_display_power_controller_feature
+ // parameter: use_newly_structured_display_power_controller
+ public boolean isNewPowerControllerFeatureEnabled() {
+ return mDeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_NEW_POWER_CONTROLLER, true);
+ }
+
+ // feature: hdr_output_control
+ // parameter: enable_hdr_output_control
+ public boolean isHdrOutputControlFeatureEnabled() {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.HDR_OUTPUT_CONTROL_FLAG, true);
+ }
+
+ // feature: flexible_brightness_range_feature
+ // parameter: normal_brightness_mode_controller_enabled
+ public boolean isNormalBrightnessControllerFeatureEnabled() {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER, false);
+ }
+
+ // feature: smooth_display_feature
+ // parameter: peak_refresh_rate_default
+ public float getPeakRefreshRateDefault() {
+ return mDeviceConfig.getFloat(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1);
+ }
+
+ // Test parameters
+ // usage e.g.: adb shell device_config put display_manager refresh_rate_in_hbm_sunlight 90
+
+ // allows to customize brightness throttling data
+ public String getBrightnessThrottlingData() {
+ return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, null);
+ }
+
+ public int getRefreshRateInHbmSunlight() {
+ return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, -1);
+ }
+
+ public int getRefreshRateInHbmHdr() {
+ return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR, -1);
+ }
+
+
+ public int getRefreshRateInHighZone() {
+ return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, -1);
+ }
+
+ public int getRefreshRateInLowZone() {
+ return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1);
+ }
+
+ /** Return null if no such property or wrong format (not comma separated integers). */
+ @Nullable
+ public int[] getHighAmbientBrightnessThresholds() {
+ return getIntArrayProperty(DisplayManager.DeviceConfig
+ .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS);
+ }
+
+ /** Return null if no such property or wrong format (not comma separated integers). */
+ @Nullable
+ public int[] getHighDisplayBrightnessThresholds() {
+ return getIntArrayProperty(DisplayManager.DeviceConfig
+ .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS);
+ }
+
+ /** Return null if no such property or wrong format (not comma separated integers). */
+ @Nullable
+ public int[] getLowDisplayBrightnessThresholds() {
+ return getIntArrayProperty(DisplayManager.DeviceConfig
+ .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS);
+ }
+
+ /** Return null if no such property or wrong format (not comma separated integers). */
+ @Nullable
+ public int[] getLowAmbientBrightnessThresholds() {
+ return getIntArrayProperty(DisplayManager.DeviceConfig
+ .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS);
+ }
+
+ /** add property change listener to DeviceConfig */
+ public void addOnPropertiesChangedListener(Executor executor,
+ DeviceConfig.OnPropertiesChangedListener listener) {
+ mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ executor, listener);
+ }
+
+ /** remove property change listener from DeviceConfig */
+ public void removeOnPropertiesChangedListener(
+ DeviceConfig.OnPropertiesChangedListener listener) {
+ mDeviceConfig.removeOnPropertiesChangedListener(listener);
+ }
+
+ @Nullable
+ private int[] getIntArrayProperty(String prop) {
+ String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop,
+ null);
+
+ if (strArray != null) {
+ return parseIntArray(strArray);
+ }
+ return null;
+ }
+
+ @Nullable
+ private int[] parseIntArray(@NonNull String strArray) {
+ String[] items = strArray.split(",");
+ int[] array = new int[items.length];
+
+ try {
+ for (int i = 0; i < array.length; i++) {
+ array[i] = Integer.parseInt(items[i]);
+ }
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e);
+ array = null;
+ }
+
+ return array;
+ }
+}
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index b55d7d5..d9ec3de 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -22,6 +22,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Slog;
import android.view.DisplayAddress;
@@ -77,7 +79,7 @@
DisplayIdProducer idProducer) {
createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true,
DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN,
- NO_LEAD_DISPLAY, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
/* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
}
@@ -90,19 +92,20 @@
* @param displayGroupName Name of the display group to which the display is assigned.
* @param idProducer Produces the logical display id.
* @param position Indicates the position this display is facing in this layout.
- * @param leadDisplayId Display that this one follows (-1 if none).
+ * @param leadDisplayAddress Address of a display that this one follows ({@code null} if none).
* @param brightnessThrottlingMapId Name of which brightness throttling policy should be used.
* @param refreshRateZoneId Layout limited refresh rate zone name.
* @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling
* policy should be used.
-
+ *
* @exception IllegalArgumentException When a default display owns a display group other than
* DEFAULT_DISPLAY_GROUP.
*/
public void createDisplayLocked(
@NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
- String displayGroupName, DisplayIdProducer idProducer, int position, int leadDisplayId,
- String brightnessThrottlingMapId, @Nullable String refreshRateZoneId,
+ String displayGroupName, DisplayIdProducer idProducer, int position,
+ @Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId,
+ @Nullable String refreshRateZoneId,
@Nullable String refreshRateThermalThrottlingMapId) {
if (contains(address)) {
Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
@@ -115,21 +118,27 @@
return;
}
- // Assign a logical display ID and create the new display.
- // Note that the logical display ID is saved into the layout, so when switching between
- // different layouts, a logical display can be destroyed and later recreated with the
- // same logical display ID.
if (displayGroupName == null) {
displayGroupName = DEFAULT_DISPLAY_GROUP_NAME;
}
if (isDefault && !displayGroupName.equals(DEFAULT_DISPLAY_GROUP_NAME)) {
throw new IllegalArgumentException("Default display should own DEFAULT_DISPLAY_GROUP");
}
+ if (isDefault && leadDisplayAddress != null) {
+ throw new IllegalArgumentException("Default display cannot have a lead display");
+ }
+ if (address.equals(leadDisplayAddress)) {
+ throw new IllegalArgumentException("Lead display address cannot be the same as display "
+ + " address");
+ }
+ // Assign a logical display ID and create the new display.
+ // Note that the logical display ID is saved into the layout, so when switching between
+ // different layouts, a logical display can be destroyed and later recreated with the
+ // same logical display ID.
final int logicalDisplayId = idProducer.getId(isDefault);
- leadDisplayId = isDefault ? NO_LEAD_DISPLAY : leadDisplayId;
final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName,
- brightnessThrottlingMapId, position, leadDisplayId, refreshRateZoneId,
+ brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId,
refreshRateThermalThrottlingMapId);
mDisplays.add(display);
@@ -146,6 +155,43 @@
}
/**
+ * Applies post-processing to displays to make sure the information of each display is
+ * up-to-date.
+ *
+ * <p>At creation of a display, lead display is specified by display address. At post
+ * processing, we convert it to logical display ID.
+ */
+ public void postProcessLocked() {
+ for (int i = 0; i < mDisplays.size(); i++) {
+ Display display = mDisplays.get(i);
+ if (display.getLogicalDisplayId() == DEFAULT_DISPLAY) {
+ display.setLeadDisplayId(NO_LEAD_DISPLAY);
+ continue;
+ }
+ DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress();
+ if (leadDisplayAddress == null) {
+ display.setLeadDisplayId(NO_LEAD_DISPLAY);
+ continue;
+ }
+ Display leadDisplay = getByAddress(leadDisplayAddress);
+ if (leadDisplay == null) {
+ throw new IllegalArgumentException("Cannot find a lead display whose address is "
+ + leadDisplayAddress);
+ }
+ if (!TextUtils.equals(display.getDisplayGroupName(),
+ leadDisplay.getDisplayGroupName())) {
+ throw new IllegalArgumentException("Lead display(" + leadDisplay + ") should be in "
+ + "the same display group of the display(" + display + ")");
+ }
+ if (hasCyclicLeadDisplay(display)) {
+ throw new IllegalArgumentException("Display(" + display + ") has a cyclic lead "
+ + "display");
+ }
+ display.setLeadDisplayId(leadDisplay.getLogicalDisplayId());
+ }
+ }
+
+ /**
* @param address The address to check.
*
* @return True if the specified address is used in this layout.
@@ -208,6 +254,20 @@
return mDisplays.size();
}
+ private boolean hasCyclicLeadDisplay(Display display) {
+ ArraySet<Display> visited = new ArraySet<>();
+
+ while (display != null) {
+ if (visited.contains(display)) {
+ return true;
+ }
+ visited.add(display);
+ DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress();
+ display = leadDisplayAddress == null ? null : getByAddress(leadDisplayAddress);
+ }
+ return false;
+ }
+
/**
* Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
*/
@@ -240,8 +300,9 @@
@Nullable
private final String mThermalBrightnessThrottlingMapId;
- // The ID of the lead display that this display will follow in a layout. -1 means no lead.
- private final int mLeadDisplayId;
+ // The address of the lead display that is specified in display-layout-configuration.
+ @Nullable
+ private final DisplayAddress mLeadDisplayAddress;
// Refresh rate zone id for specific layout
@Nullable
@@ -250,9 +311,13 @@
@Nullable
private final String mThermalRefreshRateThrottlingMapId;
+ // The ID of the lead display that this display will follow in a layout. -1 means no lead.
+ // This is determined using {@code mLeadDisplayAddress}.
+ private int mLeadDisplayId;
+
private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
@NonNull String displayGroupName, String brightnessThrottlingMapId, int position,
- int leadDisplayId, @Nullable String refreshRateZoneId,
+ @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId,
@Nullable String refreshRateThermalThrottlingMapId) {
mAddress = address;
mLogicalDisplayId = logicalDisplayId;
@@ -260,9 +325,10 @@
mDisplayGroupName = displayGroupName;
mPosition = position;
mThermalBrightnessThrottlingMapId = brightnessThrottlingMapId;
+ mLeadDisplayAddress = leadDisplayAddress;
mRefreshRateZoneId = refreshRateZoneId;
mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId;
- mLeadDisplayId = leadDisplayId;
+ mLeadDisplayId = NO_LEAD_DISPLAY;
}
@Override
@@ -276,6 +342,7 @@
+ ", mThermalBrightnessThrottlingMapId: " + mThermalBrightnessThrottlingMapId
+ ", mRefreshRateZoneId: " + mRefreshRateZoneId
+ ", mLeadDisplayId: " + mLeadDisplayId
+ + ", mLeadDisplayAddress: " + mLeadDisplayAddress
+ ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId
+ "}";
}
@@ -297,6 +364,7 @@
otherDisplay.mThermalBrightnessThrottlingMapId)
&& Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId)
&& this.mLeadDisplayId == otherDisplay.mLeadDisplayId
+ && Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress)
&& Objects.equals(mThermalRefreshRateThrottlingMapId,
otherDisplay.mThermalRefreshRateThrottlingMapId);
}
@@ -309,9 +377,10 @@
result = 31 * result + mLogicalDisplayId;
result = 31 * result + mDisplayGroupName.hashCode();
result = 31 * result + mAddress.hashCode();
- result = 31 * result + mThermalBrightnessThrottlingMapId.hashCode();
+ result = 31 * result + Objects.hashCode(mThermalBrightnessThrottlingMapId);
result = 31 * result + Objects.hashCode(mRefreshRateZoneId);
result = 31 * result + mLeadDisplayId;
+ result = 31 * result + Objects.hashCode(mLeadDisplayAddress);
result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId);
return result;
}
@@ -360,8 +429,20 @@
return mLeadDisplayId;
}
+ /**
+ * @return Display address of the display that this one follows.
+ */
+ @Nullable
+ public DisplayAddress getLeadDisplayAddress() {
+ return mLeadDisplayAddress;
+ }
+
public String getRefreshRateThermalThrottlingMapId() {
return mThermalRefreshRateThrottlingMapId;
}
+
+ private void setLeadDisplayId(int id) {
+ mLeadDisplayId = id;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 1889578..11e35ce 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -20,6 +20,7 @@
import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE;
import static android.os.PowerManager.BRIGHTNESS_INVALID;
+import android.annotation.IntegerRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
@@ -68,6 +69,7 @@
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.utils.AmbientFilter;
import com.android.server.display.utils.AmbientFilterFactory;
import com.android.server.display.utils.SensorUtils;
@@ -84,6 +86,7 @@
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.Callable;
+import java.util.function.IntSupplier;
/**
* The DisplayModeDirector is responsible for determining what modes are allowed to be automatically
@@ -117,7 +120,7 @@
private final SensorObserver mSensorObserver;
private final HbmObserver mHbmObserver;
private final SkinThermalStatusObserver mSkinThermalStatusObserver;
- private final DeviceConfigInterface mDeviceConfig;
+ private final DeviceConfigParameterProvider mConfigParameterProvider;
private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
@GuardedBy("mLock")
@@ -157,7 +160,7 @@
mSupportedModesByDisplay = new SparseArray<>();
mDefaultModeByDisplay = new SparseArray<>();
mAppRequestObserver = new AppRequestObserver();
- mDeviceConfig = injector.getDeviceConfig();
+ mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
mSettingsObserver = new SettingsObserver(context, handler);
mBrightnessObserver = new BrightnessObserver(context, handler, injector);
@@ -681,9 +684,9 @@
synchronized (mLock) {
mDefaultDisplayDeviceConfig = displayDeviceConfig;
mSettingsObserver.setRefreshRates(displayDeviceConfig,
- /* attemptLoadingFromDeviceConfig= */ true);
+ /* attemptReadFromFeatureParams= */ true);
mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
- /* attemptLoadingFromDeviceConfig= */ true);
+ /* attemptReadFromFeatureParams= */ true);
mBrightnessObserver.reloadLightSensor(displayDeviceConfig);
mHbmObserver.setupHdrRefreshRates(displayDeviceConfig);
}
@@ -1087,7 +1090,7 @@
// reading from the DeviceConfig is an intensive IO operation and having it in the
// startup phase where we thrive to keep the latency very low has significant impact.
setRefreshRates(/* displayDeviceConfig= */ null,
- /* attemptLoadingFromDeviceConfig= */ false);
+ /* attemptReadFromFeatureParams= */ false);
}
/**
@@ -1095,8 +1098,8 @@
* if missing from DisplayDeviceConfig, and finally fallback to config.xml.
*/
public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig,
- boolean attemptLoadingFromDeviceConfig) {
- setDefaultPeakRefreshRate(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+ boolean attemptReadFromFeatureParams) {
+ setDefaultPeakRefreshRate(displayDeviceConfig, attemptReadFromFeatureParams);
mDefaultRefreshRate =
(displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
R.integer.config_defaultRefreshRate)
@@ -1113,9 +1116,9 @@
cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/,
this);
- Float deviceConfigDefaultPeakRefresh =
- mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
- if (deviceConfigDefaultPeakRefresh != null) {
+ float deviceConfigDefaultPeakRefresh =
+ mConfigParameterProvider.getPeakRefreshRateDefault();
+ if (deviceConfigDefaultPeakRefresh != -1) {
mDefaultPeakRefreshRate = deviceConfigDefaultPeakRefresh;
}
@@ -1137,7 +1140,7 @@
synchronized (mLock) {
if (defaultPeakRefreshRate == null) {
setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig,
- /* attemptLoadingFromDeviceConfig= */ false);
+ /* attemptReadFromFeatureParams= */ false);
updateRefreshRateSettingLocked();
} else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) {
mDefaultPeakRefreshRate = defaultPeakRefreshRate;
@@ -1171,18 +1174,17 @@
}
private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig,
- boolean attemptLoadingFromDeviceConfig) {
- Float defaultPeakRefreshRate = null;
+ boolean attemptReadFromFeatureParams) {
+ float defaultPeakRefreshRate = -1;
- if (attemptLoadingFromDeviceConfig) {
+ if (attemptReadFromFeatureParams) {
try {
- defaultPeakRefreshRate =
- mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
+ defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault();
} catch (Exception exception) {
// Do nothing
}
}
- if (defaultPeakRefreshRate == null) {
+ if (defaultPeakRefreshRate == -1) {
defaultPeakRefreshRate =
(displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
R.integer.config_defaultPeakRefreshRate)
@@ -1528,7 +1530,7 @@
mHandler = handler;
mInjector = injector;
updateBlockingZoneThresholds(/* displayDeviceConfig= */ null,
- /* attemptLoadingFromDeviceConfig= */ false);
+ /* attemptReadFromFeatureParams= */ false);
mRefreshRateInHighZone = context.getResources().getInteger(
R.integer.config_fixedRefreshRateInHighZone);
}
@@ -1537,10 +1539,10 @@
* This is used to update the blocking zone thresholds from the DeviceConfig, which
* if missing from DisplayDeviceConfig, and finally fallback to config.xml.
*/
- public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig,
- boolean attemptLoadingFromDeviceConfig) {
- loadLowBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
- loadHighBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+ public void updateBlockingZoneThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptReadFromFeatureParams) {
+ loadLowBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams);
+ loadHighBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams);
}
@VisibleForTesting
@@ -1579,20 +1581,20 @@
return mRefreshRateInLowZone;
}
- private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
- boolean attemptLoadingFromDeviceConfig) {
- loadRefreshRateInHighZone(displayDeviceConfig, attemptLoadingFromDeviceConfig);
- loadRefreshRateInLowZone(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+ private void loadLowBrightnessThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptReadFromFeatureParams) {
+ loadRefreshRateInHighZone(displayDeviceConfig, attemptReadFromFeatureParams);
+ loadRefreshRateInLowZone(displayDeviceConfig, attemptReadFromFeatureParams);
mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
- () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
+ () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
() -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
R.array.config_brightnessThresholdsOfPeakRefreshRate,
- displayDeviceConfig, attemptLoadingFromDeviceConfig);
+ displayDeviceConfig, attemptReadFromFeatureParams);
mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
- () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
+ () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(),
() -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
R.array.config_ambientThresholdsOfPeakRefreshRate,
- displayDeviceConfig, attemptLoadingFromDeviceConfig);
+ displayDeviceConfig, attemptReadFromFeatureParams);
if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) {
throw new RuntimeException("display low brightness threshold array and ambient "
+ "brightness threshold array have different length: "
@@ -1604,55 +1606,55 @@
}
private void loadRefreshRateInLowZone(DisplayDeviceConfig displayDeviceConfig,
- boolean attemptLoadingFromDeviceConfig) {
- int refreshRateInLowZone =
- (displayDeviceConfig == null) ? mContext.getResources().getInteger(
- R.integer.config_defaultRefreshRateInZone)
- : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate();
- if (attemptLoadingFromDeviceConfig) {
+ boolean attemptReadFromFeatureParams) {
+ int refreshRateInLowZone = -1;
+ if (attemptReadFromFeatureParams) {
try {
- refreshRateInLowZone = mDeviceConfig.getInt(
- DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE,
- refreshRateInLowZone);
+ refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone();
} catch (Exception exception) {
// Do nothing
}
}
+ if (refreshRateInLowZone == -1) {
+ refreshRateInLowZone = (displayDeviceConfig == null)
+ ? mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRateInZone)
+ : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate();
+ }
mRefreshRateInLowZone = refreshRateInLowZone;
}
private void loadRefreshRateInHighZone(DisplayDeviceConfig displayDeviceConfig,
- boolean attemptLoadingFromDeviceConfig) {
- int refreshRateInHighZone =
- (displayDeviceConfig == null) ? mContext.getResources().getInteger(
- R.integer.config_fixedRefreshRateInHighZone) : displayDeviceConfig
- .getDefaultHighBlockingZoneRefreshRate();
- if (attemptLoadingFromDeviceConfig) {
+ boolean attemptReadFromFeatureParams) {
+ int refreshRateInHighZone = -1;
+ if (attemptReadFromFeatureParams) {
try {
- refreshRateInHighZone = mDeviceConfig.getInt(
- DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
- refreshRateInHighZone);
+ refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone();
} catch (Exception exception) {
// Do nothing
}
}
+ if (refreshRateInHighZone == -1) {
+ refreshRateInHighZone = (displayDeviceConfig == null)
+ ? mContext.getResources().getInteger(
+ R.integer.config_fixedRefreshRateInHighZone)
+ : displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate();
+ }
mRefreshRateInHighZone = refreshRateInHighZone;
}
private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
- boolean attemptLoadingFromDeviceConfig) {
+ boolean attemptReadFromFeatureParams) {
mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
- () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
+ () -> mConfigParameterProvider.getHighDisplayBrightnessThresholds(),
() -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
- displayDeviceConfig, attemptLoadingFromDeviceConfig);
+ displayDeviceConfig, attemptReadFromFeatureParams);
mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
- () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
+ () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(),
() -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
- displayDeviceConfig, attemptLoadingFromDeviceConfig);
+ displayDeviceConfig, attemptReadFromFeatureParams);
if (mHighDisplayBrightnessThresholds.length
!= mHighAmbientBrightnessThresholds.length) {
throw new RuntimeException("display high brightness threshold array and ambient "
@@ -1668,10 +1670,10 @@
Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable,
Callable<int[]> loadFromDisplayDeviceConfigCallable,
int brightnessThresholdOfFixedRefreshRateKey,
- DisplayDeviceConfig displayDeviceConfig, boolean attemptLoadingFromDeviceConfig) {
+ DisplayDeviceConfig displayDeviceConfig, boolean attemptReadFromFeatureParams) {
int[] brightnessThresholds = null;
- if (attemptLoadingFromDeviceConfig) {
+ if (attemptReadFromFeatureParams) {
try {
brightnessThresholds =
loadFromDeviceConfigDisplaySettingsCallable.call();
@@ -1715,9 +1717,9 @@
// DeviceConfig is accessible after system ready.
int[] lowDisplayBrightnessThresholds =
- mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds();
+ mConfigParameterProvider.getLowDisplayBrightnessThresholds();
int[] lowAmbientBrightnessThresholds =
- mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds();
+ mConfigParameterProvider.getLowAmbientBrightnessThresholds();
if (lowDisplayBrightnessThresholds != null && lowAmbientBrightnessThresholds != null
&& lowDisplayBrightnessThresholds.length
@@ -1727,9 +1729,9 @@
}
int[] highDisplayBrightnessThresholds =
- mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds();
+ mConfigParameterProvider.getHighDisplayBrightnessThresholds();
int[] highAmbientBrightnessThresholds =
- mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds();
+ mConfigParameterProvider.getHighAmbientBrightnessThresholds();
if (highDisplayBrightnessThresholds != null && highAmbientBrightnessThresholds != null
&& highDisplayBrightnessThresholds.length
@@ -1738,14 +1740,12 @@
mHighAmbientBrightnessThresholds = highAmbientBrightnessThresholds;
}
- final int refreshRateInLowZone = mDeviceConfigDisplaySettings
- .getRefreshRateInLowZone();
+ final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone();
if (refreshRateInLowZone != -1) {
mRefreshRateInLowZone = refreshRateInLowZone;
}
- final int refreshRateInHighZone = mDeviceConfigDisplaySettings
- .getRefreshRateInHighZone();
+ final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone();
if (refreshRateInHighZone != -1) {
mRefreshRateInHighZone = refreshRateInHighZone;
}
@@ -1799,15 +1799,15 @@
displayDeviceConfig = mDefaultDisplayDeviceConfig;
}
mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
- () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
+ () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
() -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
R.array.config_brightnessThresholdsOfPeakRefreshRate,
- displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+ displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
- () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
+ () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(),
() -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
R.array.config_ambientThresholdsOfPeakRefreshRate,
- displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+ displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
}
restartObserver();
}
@@ -1822,7 +1822,7 @@
// from there.
synchronized (mLock) {
loadRefreshRateInLowZone(mDefaultDisplayDeviceConfig,
- /* attemptLoadingFromDeviceConfig= */ false);
+ /* attemptReadFromFeatureParams= */ false);
}
restartObserver();
} else if (refreshRate != mRefreshRateInLowZone) {
@@ -1843,15 +1843,15 @@
displayDeviceConfig = mDefaultDisplayDeviceConfig;
}
mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
- () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
+ () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
() -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
- displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+ displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
- () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
+ () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(),
() -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
- displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+ displayDeviceConfig, /* attemptReadFromFeatureParams= */ false);
}
restartObserver();
}
@@ -1866,7 +1866,7 @@
// from there.
synchronized (mLock) {
loadRefreshRateInHighZone(mDefaultDisplayDeviceConfig,
- /* attemptLoadingFromDeviceConfig= */ false);
+ /* attemptReadFromFeatureParams= */ false);
}
restartObserver();
} else if (refreshRate != mRefreshRateInHighZone) {
@@ -2675,113 +2675,55 @@
private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener {
public void startListening() {
- mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ mConfigParameterProvider.addOnPropertiesChangedListener(
BackgroundThread.getExecutor(), this);
}
- /*
- * Return null if no such property or wrong format (not comma separated integers).
- */
- public int[] getLowDisplayBrightnessThresholds() {
- return getIntArrayProperty(
- DisplayManager.DeviceConfig
- .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS);
+ private int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) {
+ return getRefreshRate(
+ () -> mConfigParameterProvider.getRefreshRateInHbmHdr(),
+ () -> displayDeviceConfig.getDefaultRefreshRateInHbmHdr(),
+ R.integer.config_defaultRefreshRateInHbmHdr,
+ displayDeviceConfig
+ );
}
- /*
- * Return null if no such property or wrong format (not comma separated integers).
- */
- public int[] getLowAmbientBrightnessThresholds() {
- return getIntArrayProperty(
- DisplayManager.DeviceConfig
- .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS);
+ private int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) {
+ return getRefreshRate(
+ () -> mConfigParameterProvider.getRefreshRateInHbmSunlight(),
+ () -> displayDeviceConfig.getDefaultRefreshRateInHbmSunlight(),
+ R.integer.config_defaultRefreshRateInHbmSunlight,
+ displayDeviceConfig
+ );
}
- public int getRefreshRateInLowZone() {
- return mDeviceConfig.getInt(
- DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1);
-
- }
-
- /*
- * Return null if no such property or wrong format (not comma separated integers).
- */
- public int[] getHighDisplayBrightnessThresholds() {
- return getIntArrayProperty(
- DisplayManager.DeviceConfig
- .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS);
- }
-
- /*
- * Return null if no such property or wrong format (not comma separated integers).
- */
- public int[] getHighAmbientBrightnessThresholds() {
- return getIntArrayProperty(
- DisplayManager.DeviceConfig
- .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS);
- }
-
- public int getRefreshRateInHighZone() {
- return mDeviceConfig.getInt(
- DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
- -1);
- }
-
- public int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) {
- int refreshRate =
- (displayDeviceConfig == null) ? mContext.getResources().getInteger(
- R.integer.config_defaultRefreshRateInHbmHdr)
- : displayDeviceConfig.getDefaultRefreshRateInHbmHdr();
+ private int getRefreshRate(IntSupplier fromConfigPram, IntSupplier fromDisplayDeviceConfig,
+ @IntegerRes int configKey, DisplayDeviceConfig displayDeviceConfig) {
+ int refreshRate = -1;
try {
- refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR,
- refreshRate);
- } catch (NullPointerException e) {
+ refreshRate = fromConfigPram.getAsInt();
+ } catch (NullPointerException npe) {
// Do Nothing
}
- return refreshRate;
- }
-
- public int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) {
- int refreshRate =
- (displayDeviceConfig == null) ? mContext.getResources()
- .getInteger(R.integer.config_defaultRefreshRateInHbmSunlight)
- : displayDeviceConfig.getDefaultRefreshRateInHbmSunlight();
- try {
- refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT,
- refreshRate);
- } catch (NullPointerException e) {
- // Do Nothing
+ if (refreshRate == -1) {
+ refreshRate = (displayDeviceConfig == null)
+ ? mContext.getResources().getInteger(configKey)
+ : fromDisplayDeviceConfig.getAsInt();
}
return refreshRate;
}
- /*
- * Return null if no such property
- */
- public Float getDefaultPeakRefreshRate() {
- float defaultPeakRefreshRate = mDeviceConfig.getFloat(
- DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1);
-
- if (defaultPeakRefreshRate == -1) {
- return null;
- }
- return defaultPeakRefreshRate;
- }
-
@Override
public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
- Float defaultPeakRefreshRate = getDefaultPeakRefreshRate();
+ float defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault();
mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED,
- defaultPeakRefreshRate).sendToTarget();
+ defaultPeakRefreshRate == -1 ? null : defaultPeakRefreshRate).sendToTarget();
- int[] lowDisplayBrightnessThresholds = getLowDisplayBrightnessThresholds();
- int[] lowAmbientBrightnessThresholds = getLowAmbientBrightnessThresholds();
- final int refreshRateInLowZone = getRefreshRateInLowZone();
+ int[] lowDisplayBrightnessThresholds =
+ mConfigParameterProvider.getLowDisplayBrightnessThresholds();
+ int[] lowAmbientBrightnessThresholds =
+ mConfigParameterProvider.getLowAmbientBrightnessThresholds();
+ final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone();
mHandler.obtainMessage(MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED,
new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds))
@@ -2790,9 +2732,11 @@
mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone,
0).sendToTarget();
- int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds();
- int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds();
- final int refreshRateInHighZone = getRefreshRateInHighZone();
+ int[] highDisplayBrightnessThresholds =
+ mConfigParameterProvider.getHighDisplayBrightnessThresholds();
+ int[] highAmbientBrightnessThresholds =
+ mConfigParameterProvider.getHighAmbientBrightnessThresholds();
+ final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone();
mHandler.obtainMessage(MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED,
new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds))
@@ -2814,33 +2758,6 @@
.sendToTarget();
}
}
-
- private int[] getIntArrayProperty(String prop) {
- String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop,
- null);
-
- if (strArray != null) {
- return parseIntArray(strArray);
- }
-
- return null;
- }
-
- private int[] parseIntArray(@NonNull String strArray) {
- String[] items = strArray.split(",");
- int[] array = new int[items.length];
-
- try {
- for (int i = 0; i < array.length; i++) {
- array[i] = Integer.parseInt(items[i]);
- }
- } catch (NumberFormatException e) {
- Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e);
- array = null;
- }
-
- return array;
- }
}
interface Injector {
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 6d70d21..633bf731 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -16,11 +16,11 @@
package com.android.server.dreams;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
import android.app.ActivityTaskManager;
import android.app.BroadcastOptions;
+import android.app.IAppTask;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -214,6 +214,27 @@
}
/**
+ * Provides an appTask for the dream with token {@code dreamToken}, so that the dream controller
+ * can stop the dream task when necessary.
+ */
+ void setDreamAppTask(Binder dreamToken, IAppTask appTask) {
+ if (mCurrentDream == null || mCurrentDream.mToken != dreamToken
+ || mCurrentDream.mAppTask != null) {
+ Slog.e(TAG, "Illegal dream activity start. mCurrentDream.mToken = "
+ + mCurrentDream.mToken + ", illegal dreamToken = " + dreamToken
+ + ". Ending this dream activity.");
+ try {
+ appTask.finishAndRemoveTask();
+ } catch (RemoteException | RuntimeException e) {
+ Slog.e(TAG, "Unable to stop illegal dream activity.");
+ }
+ return;
+ }
+
+ mCurrentDream.mAppTask = appTask;
+ }
+
+ /**
* Stops dreaming.
*
* The current dream, if any, and any unstopped previous dreams are stopped. The device stops
@@ -303,8 +324,14 @@
mSentStartBroadcast = false;
}
- mActivityTaskManager.removeRootTasksWithActivityTypes(
- new int[] {ACTIVITY_TYPE_DREAM});
+ if (mCurrentDream != null && mCurrentDream.mAppTask != null) {
+ // Finish the dream task in case it hasn't finished by itself already.
+ try {
+ mCurrentDream.mAppTask.finishAndRemoveTask();
+ } catch (RemoteException | RuntimeException e) {
+ Slog.e(TAG, "Unable to stop dream activity.");
+ }
+ }
mListener.onDreamStopped(dream.mToken);
}
@@ -364,6 +391,7 @@
public final boolean mIsPreviewMode;
public final boolean mCanDoze;
public final int mUserId;
+ public IAppTask mAppTask;
public PowerManager.WakeLock mWakeLock;
public boolean mBound;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 68cf59f..d88fe8a 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -27,6 +27,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.IAppTask;
import android.app.TaskInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -37,6 +38,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.hardware.display.AmbientDisplayConfiguration;
@@ -116,6 +118,7 @@
private final PowerManagerInternal mPowerManagerInternal;
private final PowerManager.WakeLock mDozeWakeLock;
private final ActivityTaskManagerInternal mAtmInternal;
+ private final PackageManagerInternal mPmInternal;
private final UserManager mUserManager;
private final UiEventLogger mUiEventLogger;
private final DreamUiEventLogger mDreamUiEventLogger;
@@ -216,6 +219,7 @@
mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mPowerManagerInternal = getLocalService(PowerManagerInternal.class);
mAtmInternal = getLocalService(ActivityTaskManagerInternal.class);
+ mPmInternal = getLocalService(PackageManagerInternal.class);
mUserManager = context.getSystemService(UserManager.class);
mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, DOZE_WAKE_LOCK_TAG);
mDozeConfig = new AmbientDisplayConfiguration(mContext);
@@ -1064,6 +1068,64 @@
Binder.restoreCallingIdentity(ident);
}
}
+
+ @Override // Binder call
+ public void startDreamActivity(@NonNull Intent intent) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ // We post here, because startDreamActivity and setDreamAppTask have to run
+ // synchronously and DreamController#setDreamAppTask has to run on mHandler.
+ mHandler.post(() -> {
+ final Binder dreamToken;
+ final String dreamPackageName;
+ synchronized (mLock) {
+ if (mCurrentDream == null) {
+ Slog.e(TAG, "Attempt to start DreamActivity, but the device is not "
+ + "dreaming. Aborting without starting the DreamActivity.");
+ return;
+ }
+ dreamToken = mCurrentDream.token;
+ dreamPackageName = mCurrentDream.name.getPackageName();
+ }
+
+ if (!canLaunchDreamActivity(dreamPackageName, intent.getPackage(),
+ callingUid)) {
+ Slog.e(TAG, "The dream activity can be started only when the device is dreaming"
+ + " and only by the active dream package.");
+ return;
+ }
+
+ final IAppTask appTask = mAtmInternal.startDreamActivity(intent, callingUid,
+ callingPid);
+ if (appTask == null) {
+ Slog.e(TAG, "Could not start dream activity.");
+ stopDreamInternal(true, "DreamActivity not started");
+ return;
+ }
+ mController.setDreamAppTask(dreamToken, appTask);
+ });
+ }
+
+ boolean canLaunchDreamActivity(String dreamPackageName, String packageName,
+ int callingUid) {
+ if (dreamPackageName == null || packageName == null) {
+ Slog.e(TAG, "Cannot launch dream activity due to invalid state. dream component= "
+ + dreamPackageName + ", packageName=" + packageName);
+ return false;
+ }
+ if (!mPmInternal.isSameApp(packageName, callingUid, UserHandle.getUserId(callingUid))) {
+ Slog.e(TAG, "Cannot launch dream activity because package="
+ + packageName + " does not match callingUid=" + callingUid);
+ return false;
+ }
+ if (packageName.equals(dreamPackageName)) {
+ return true;
+ }
+ Slog.e(TAG, "Dream packageName does not match active dream. Package " + packageName
+ + " does not match " + dreamPackageName);
+ return false;
+ }
+
}
private final class LocalService extends DreamManagerInternal {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 6a177e0..3a8f9d5 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2800,6 +2800,11 @@
void notifyConfigurationChanged();
/**
+ * This callback is invoked when the pointer location changes.
+ */
+ void notifyPointerLocationChanged(boolean pointerLocationEnabled);
+
+ /**
* This callback is invoked when the camera lens cover switch changes state.
* @param whenNanos the time when the change occurred
* @param lensCovered true is the lens is covered
@@ -3381,6 +3386,10 @@
}
}
+ void updatePointerLocationEnabled(boolean enabled) {
+ mWindowManagerCallbacks.notifyPointerLocationChanged(enabled);
+ }
+
void updateFocusEventDebugViewEnabled(boolean enabled) {
FocusEventDebugView view;
synchronized (mFocusEventDebugViewLock) {
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 42591f4..a608b4f 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -67,6 +67,8 @@
(reason) -> updateTouchpadRightClickZoneEnabled()),
Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
(reason) -> updateShowTouches()),
+ Map.entry(Settings.System.getUriFor(Settings.System.POINTER_LOCATION),
+ (reason) -> updatePointerLocation()),
Map.entry(
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON),
(reason) -> updateAccessibilityLargePointer()),
@@ -149,6 +151,11 @@
mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
}
+ private void updatePointerLocation() {
+ mService.updatePointerLocationEnabled(
+ getBoolean(Settings.System.POINTER_LOCATION, false));
+ }
+
private void updateShowKeyPresses() {
mService.updateFocusEventDebugViewEnabled(
getBoolean(Settings.System.SHOW_KEY_PRESSES, false));
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 6a0550b..aa99dab 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -291,6 +291,10 @@
reset(false /* reinitializing */);
}
+ void setInkWindowInitializer(Runnable inkWindowInitializer) {
+ mInkWindowInitRunnable = inkWindowInitializer;
+ }
+
private void reset(boolean reinitializing) {
if (mHandwritingEventReceiver != null) {
mHandwritingEventReceiver.dispose();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1ab83f7..02ee96a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2472,6 +2472,9 @@
curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
+ if (mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
+ mHwController.setInkWindowInitializer(new InkWindowInitializer());
+ }
return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
session.mSession, accessibilityInputMethodSessions,
(session.mChannel != null ? session.mChannel.dup() : null),
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 65e34e6..398e470 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -147,7 +147,7 @@
mInjector = injector;
mClock = injector.createClock();
mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
- mCallbackDelegate = new CallbackDelegate();
+ mCallbackDelegate = new CallbackDelegate(injector.createCallbackLooper());
mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mPackageManager = mContext.getPackageManager();
@@ -182,6 +182,11 @@
Clock createClock() {
return SystemClock::uptimeMillis;
}
+
+ /** Creates the {@link Looper} to be used when notifying callbacks. */
+ Looper createCallbackLooper() {
+ return Looper.getMainLooper();
+ }
}
@Override
@@ -268,7 +273,8 @@
dispatchStop(projection);
}
- private void addCallback(final IMediaProjectionWatcherCallback callback) {
+ @VisibleForTesting
+ void addCallback(final IMediaProjectionWatcherCallback callback) {
IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
@@ -315,6 +321,12 @@
mCallbackDelegate.dispatchStop(projection);
}
+ private void dispatchSessionSet(
+ @NonNull MediaProjectionInfo projectionInfo,
+ @Nullable ContentRecordingSession session) {
+ mCallbackDelegate.dispatchSession(projectionInfo, session);
+ }
+
/**
* Returns {@code true} when updating the current mirroring session on WM succeeded, and
* {@code false} otherwise.
@@ -335,6 +347,7 @@
if (mProjectionGrant != null) {
// Cache the session details.
mProjectionGrant.mSession = incomingSession;
+ dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);
}
return true;
}
@@ -1155,8 +1168,8 @@
private Handler mHandler;
private final Object mLock = new Object();
- public CallbackDelegate() {
- mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
+ CallbackDelegate(Looper callbackLooper) {
+ mHandler = new Handler(callbackLooper, null, true /*async*/);
mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>();
mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>();
}
@@ -1219,6 +1232,16 @@
}
}
+ public void dispatchSession(
+ @NonNull MediaProjectionInfo projectionInfo,
+ @Nullable ContentRecordingSession session) {
+ synchronized (mLock) {
+ for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
+ mHandler.post(new WatcherSessionCallback(callback, projectionInfo, session));
+ }
+ }
+ }
+
public void dispatchResize(MediaProjection projection, int width, int height) {
if (projection == null) {
Slog.e(TAG,
@@ -1335,6 +1358,29 @@
}
}
+ private static final class WatcherSessionCallback implements Runnable {
+ private final IMediaProjectionWatcherCallback mCallback;
+ private final MediaProjectionInfo mProjectionInfo;
+ private final ContentRecordingSession mSession;
+
+ WatcherSessionCallback(
+ @NonNull IMediaProjectionWatcherCallback callback,
+ @NonNull MediaProjectionInfo projectionInfo,
+ @Nullable ContentRecordingSession session) {
+ mCallback = callback;
+ mProjectionInfo = projectionInfo;
+ mSession = session;
+ }
+
+ @Override
+ public void run() {
+ try {
+ mCallback.onRecordingSessionSet(mProjectionInfo, mSession);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify content recording session changed", e);
+ }
+ }
+ }
private static String typeToString(int type) {
switch (type) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 68f49fd..d093393 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4443,7 +4443,7 @@
// Remove background token before returning notification to untrusted app, this
// ensures the app isn't able to perform background operations that are
// associated with notification interactions.
- notification.setAllowlistToken(null);
+ notification.clearAllowlistToken();
return new StatusBarNotification(
sbn.getPackageName(),
sbn.getOpPkg(),
@@ -9508,14 +9508,19 @@
* Determine whether the userId applies to the notification in question, either because
* they match exactly, or one of them is USER_ALL (which is treated as a wildcard).
*/
- private static boolean notificationMatchesUserId(NotificationRecord r, int userId) {
- return
+ private static boolean notificationMatchesUserId(NotificationRecord r, int userId,
+ boolean isAutogroupSummary) {
+ if (isAutogroupSummary) {
+ return r.getUserId() == userId;
+ } else {
+ return
// looking for USER_ALL notifications? match everything
- userId == UserHandle.USER_ALL
- // a notification sent to USER_ALL matches any query
- || r.getUserId() == UserHandle.USER_ALL
- // an exact user match
- || r.getUserId() == userId;
+ userId == UserHandle.USER_ALL
+ // a notification sent to USER_ALL matches any query
+ || r.getUserId() == UserHandle.USER_ALL
+ // an exact user match
+ || r.getUserId() == userId;
+ }
}
/**
@@ -9524,7 +9529,7 @@
* because it matches one of the users profiles.
*/
private boolean notificationMatchesCurrentProfiles(NotificationRecord r, int userId) {
- return notificationMatchesUserId(r, userId)
+ return notificationMatchesUserId(r, userId, false)
|| mUserProfiles.isCurrentProfile(r.getUserId());
}
@@ -9593,7 +9598,7 @@
if (!notificationMatchesCurrentProfiles(r, userId)) {
continue;
}
- } else if (!notificationMatchesUserId(r, userId)) {
+ } else if (!notificationMatchesUserId(r, userId, false)) {
continue;
}
// Don't remove notifications to all, if there's no package name specified
@@ -9831,7 +9836,7 @@
final int len = list.size();
for (int i = 0; i < len; i++) {
NotificationRecord r = list.get(i);
- if (notificationMatchesUserId(r, userId) && r.getGroupKey().equals(groupKey)
+ if (notificationMatchesUserId(r, userId, false) && r.getGroupKey().equals(groupKey)
&& r.getSbn().getPackageName().equals(pkg)) {
records.add(r);
}
@@ -9873,8 +9878,8 @@
final int len = list.size();
for (int i = 0; i < len; i++) {
NotificationRecord r = list.get(i);
- if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id &&
- TextUtils.equals(r.getSbn().getTag(), tag)
+ if (notificationMatchesUserId(r, userId, (r.getFlags() & GroupHelper.BASE_FLAGS) != 0)
+ && r.getSbn().getId() == id && TextUtils.equals(r.getSbn().getTag(), tag)
&& r.getSbn().getPackageName().equals(pkg)) {
return r;
}
@@ -9888,8 +9893,8 @@
final int len = list.size();
for (int i = 0; i < len; i++) {
NotificationRecord r = list.get(i);
- if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id &&
- TextUtils.equals(r.getSbn().getTag(), tag)
+ if (notificationMatchesUserId(r, userId, false) && r.getSbn().getId() == id
+ && TextUtils.equals(r.getSbn().getTag(), tag)
&& r.getSbn().getPackageName().equals(pkg)) {
matching.add(r);
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 5f52c16..c1d5af5 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -610,7 +610,8 @@
PackageManager.UNINSTALL_REASON_UNKNOWN,
null /*harmfulAppWarning*/,
null /*splashScreenTheme*/,
- 0 /*firstInstallTime*/);
+ 0 /*firstInstallTime*/,
+ PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
}
mPm.mSettings.writeKernelMappingLPr(ps);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index db47306..2fc22bf 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -30,6 +30,7 @@
import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
import static android.os.Process.INVALID_UID;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
@@ -5239,6 +5240,20 @@
}
@Override
+ @PackageManager.UserMinAspectRatio
+ public int getUserMinAspectRatio(@NonNull String packageName, int userId) {
+ final Computer snapshot = snapshotComputer();
+ final int callingUid = Binder.getCallingUid();
+ snapshot.enforceCrossUserPermission(
+ callingUid, userId, false /* requireFullPermission */,
+ false /* checkShell */, "getUserMinAspectRatio");
+ final PackageStateInternal packageState = snapshot
+ .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId);
+ return packageState == null ? USER_MIN_ASPECT_RATIO_UNSET
+ : packageState.getUserStateOrDefault(userId).getMinAspectRatio();
+ }
+
+ @Override
public Bundle getSuspendedPackageAppExtras(String packageName, int userId) {
final int callingUid = Binder.getCallingUid();
final Computer snapshot = snapshot();
@@ -6201,6 +6216,32 @@
return true;
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.INSTALL_PACKAGES)
+ @Override
+ public void setUserMinAspectRatio(@NonNull String packageName, int userId,
+ @PackageManager.UserMinAspectRatio int aspectRatio) {
+ setUserMinAspectRatio_enforcePermission();
+ final int callingUid = Binder.getCallingUid();
+ final Computer snapshot = snapshotComputer();
+ snapshot.enforceCrossUserPermission(callingUid, userId,
+ false /* requireFullPermission */, false /* checkShell */,
+ "setUserMinAspectRatio");
+ enforceOwnerRights(snapshot, packageName, callingUid);
+
+ final PackageStateInternal packageState = snapshot
+ .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId);
+ if (packageState == null) {
+ return;
+ }
+
+ if (packageState.getUserStateOrDefault(userId).getMinAspectRatio() == aspectRatio) {
+ return;
+ }
+
+ commitPackageStateMutation(null, packageName, state ->
+ state.userState(userId).setMinAspectRatio(aspectRatio));
+ }
+
@Override
@SuppressWarnings("GuardedBy")
public void setRuntimePermissionsVersion(int version, @UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 2411820..3e9ccac 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -875,7 +875,7 @@
ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
int installReason, int uninstallReason,
String harmfulAppWarning, String splashScreenTheme,
- long firstInstallTime) {
+ long firstInstallTime, int aspectRatio) {
modifyUserState(userId)
.setSuspendParams(suspendParams)
.setCeDataInode(ceDataInode)
@@ -894,7 +894,8 @@
.setVirtualPreload(virtualPreload)
.setHarmfulAppWarning(harmfulAppWarning)
.setSplashScreenTheme(splashScreenTheme)
- .setFirstInstallTimeMillis(firstInstallTime);
+ .setFirstInstallTimeMillis(firstInstallTime)
+ .setMinAspectRatio(aspectRatio);
onChanged();
}
@@ -912,7 +913,7 @@
? null : otherState.getDisabledComponentsNoCopy().untrackedStorage(),
otherState.getInstallReason(), otherState.getUninstallReason(),
otherState.getHarmfulAppWarning(), otherState.getSplashScreenTheme(),
- otherState.getFirstInstallTimeMillis());
+ otherState.getFirstInstallTimeMillis(), otherState.getMinAspectRatio());
}
WatchedArraySet<String> getEnabledComponents(int userId) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index aaf13eb..532ae71 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -351,6 +351,7 @@
private static final String ATTR_VIRTUAL_PRELOAD = "virtual-preload";
private static final String ATTR_HARMFUL_APP_WARNING = "harmful-app-warning";
private static final String ATTR_SPLASH_SCREEN_THEME = "splash-screen-theme";
+ private static final String ATTR_MIN_ASPECT_RATIO = "min-aspect-ratio";
private static final String ATTR_PACKAGE_NAME = "packageName";
private static final String ATTR_BUILD_FINGERPRINT = "buildFingerprint";
@@ -1122,7 +1123,8 @@
PackageManager.UNINSTALL_REASON_UNKNOWN,
null /*harmfulAppWarning*/,
null /*splashscreenTheme*/,
- 0 /*firstInstallTime*/
+ 0 /*firstInstallTime*/,
+ PackageManager.USER_MIN_ASPECT_RATIO_UNSET
);
}
}
@@ -1776,7 +1778,8 @@
PackageManager.UNINSTALL_REASON_UNKNOWN,
null /*harmfulAppWarning*/,
null /* splashScreenTheme*/,
- 0 /*firstInstallTime*/
+ 0 /*firstInstallTime*/,
+ PackageManager.USER_MIN_ASPECT_RATIO_UNSET
);
}
return;
@@ -1871,6 +1874,9 @@
ATTR_SPLASH_SCREEN_THEME);
final long firstInstallTime = parser.getAttributeLongHex(null,
ATTR_FIRST_INSTALL_TIME, 0);
+ final int minAspectRatio = parser.getAttributeInt(null,
+ ATTR_MIN_ASPECT_RATIO,
+ PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
ArraySet<String> enabledComponents = null;
ArraySet<String> disabledComponents = null;
@@ -1947,7 +1953,8 @@
enabledCaller, enabledComponents, disabledComponents, installReason,
uninstallReason, harmfulAppWarning, splashScreenTheme,
firstInstallTime != 0 ? firstInstallTime :
- origFirstInstallTimes.getOrDefault(name, 0L));
+ origFirstInstallTimes.getOrDefault(name, 0L),
+ minAspectRatio);
mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
} else if (tagName.equals("preferred-activities")) {
@@ -2242,6 +2249,11 @@
serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME,
ustate.getSplashScreenTheme());
}
+ if (ustate.getMinAspectRatio()
+ != PackageManager.USER_MIN_ASPECT_RATIO_UNSET) {
+ serializer.attributeInt(null, ATTR_MIN_ASPECT_RATIO,
+ ustate.getMinAspectRatio());
+ }
if (ustate.isSuspended()) {
for (int i = 0; i < ustate.getSuspendParams().size(); i++) {
final String suspendingPackage = ustate.getSuspendParams().keyAt(i);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 5b3514c..710e0b7 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -15,6 +15,7 @@
*/
package com.android.server.pm;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
import android.Manifest.permission;
@@ -24,6 +25,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IUidObserver;
import android.app.IUriGrantsManager;
@@ -4407,8 +4409,11 @@
return;
}
try {
+ ActivityOptions options = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_DENIED);
intentSender.sendIntent(mContext, /* code= */ 0, extras,
- /* onFinished=*/ null, /* handler= */ null);
+ /* onFinished=*/ null, /* handler= */ null, null, options.toBundle());
} catch (SendIntentException e) {
Slog.w(TAG, "sendIntent failed().", e);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7e88e13..f8bd328 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1037,7 +1037,7 @@
final UserData userData = mUsers.valueAt(i);
final int userId = userData.info.id;
if (userId != currentUser && userData.info.isFull() && !userData.info.partial
- && !mRemovingUserIds.get(userId)) {
+ && userData.info.isEnabled() && !mRemovingUserIds.get(userId)) {
final long userEnteredTime = userData.mLastEnteredForegroundTimeMillis;
if (userEnteredTime > latestEnteredTime) {
latestEnteredTime = userEnteredTime;
@@ -2927,14 +2927,14 @@
Preconditions.checkState(mCachedEffectiveUserRestrictions.getRestrictions(userId)
!= newBaseRestrictions);
- if (mBaseUserRestrictions.updateRestrictions(userId, newBaseRestrictions)) {
+ if (mBaseUserRestrictions.updateRestrictions(userId, new Bundle(newBaseRestrictions))) {
scheduleWriteUser(userId);
}
}
final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
- mCachedEffectiveUserRestrictions.updateRestrictions(userId, effective);
+ mCachedEffectiveUserRestrictions.updateRestrictions(userId, new Bundle(effective));
// Apply the new restrictions.
if (DBG) {
@@ -5589,8 +5589,14 @@
}
}
- @GuardedBy("mUsersLock")
@VisibleForTesting
+ void addRemovingUserId(@UserIdInt int userId) {
+ synchronized (mUsersLock) {
+ addRemovingUserIdLocked(userId);
+ }
+ }
+
+ @GuardedBy("mUsersLock")
void addRemovingUserIdLocked(@UserIdInt int userId) {
// We remember deleted user IDs to prevent them from being
// reused during the current boot; they can still be reused
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index 91a25db3..3d056e8 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -377,6 +377,8 @@
private final int mUninstallReason;
@Nullable
private final String mSplashScreenTheme;
+ @PackageManager.UserMinAspectRatio
+ private final int mMinAspectRatio;
private final long mFirstInstallTimeMillis;
private UserStateImpl(@NonNull PackageUserState userState) {
@@ -392,6 +394,7 @@
mSharedLibraryOverlayPaths = userState.getSharedLibraryOverlayPaths();
mUninstallReason = userState.getUninstallReason();
mSplashScreenTheme = userState.getSplashScreenTheme();
+ mMinAspectRatio = userState.getMinAspectRatio();
setBoolean(Booleans.HIDDEN, userState.isHidden());
setBoolean(Booleans.INSTALLED, userState.isInstalled());
setBoolean(Booleans.INSTANT_APP, userState.isInstantApp());
@@ -543,6 +546,11 @@
}
@DataClass.Generated.Member
+ public @PackageManager.UserMinAspectRatio int getMinAspectRatio() {
+ return mMinAspectRatio;
+ }
+
+ @DataClass.Generated.Member
public long getFirstInstallTimeMillis() {
return mFirstInstallTimeMillis;
}
@@ -554,10 +562,10 @@
}
@DataClass.Generated(
- time = 1671671043891L,
+ time = 1687938966108L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
- inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final long mFirstInstallTimeMillis\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+ inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate final long mFirstInstallTimeMillis\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
index 2048d65..f75d214 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
@@ -217,4 +217,12 @@
*/
@Nullable
String getSplashScreenTheme();
+
+ /**
+ * @return the min aspect ratio setting of the package which by default is unset
+ * unless it has been set by the user
+ * @hide
+ */
+ @PackageManager.UserMinAspectRatio
+ int getMinAspectRatio();
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
index 73fb672..1fb12a8 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
@@ -136,6 +136,11 @@
}
@Override
+ public @PackageManager.UserMinAspectRatio int getMinAspectRatio() {
+ return PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+ }
+
+ @Override
public long getFirstInstallTimeMillis() {
return 0;
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index e8e2d41..d911ac1 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -83,6 +83,9 @@
@Nullable
private String mSplashScreenTheme;
+ @PackageManager.UserMinAspectRatio
+ private int mMinAspectRatio = PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+
/**
* Suspending package to suspend params
*/
@@ -146,6 +149,7 @@
mHarmfulAppWarning = other.mHarmfulAppWarning;
mLastDisableAppCaller = other.mLastDisableAppCaller;
mSplashScreenTheme = other.mSplashScreenTheme;
+ mMinAspectRatio = other.mMinAspectRatio;
mSuspendParams = other.mSuspendParams == null ? null : other.mSuspendParams.snapshot();
mComponentLabelIconOverrideMap = other.mComponentLabelIconOverrideMap == null
? null : other.mComponentLabelIconOverrideMap.snapshot();
@@ -508,6 +512,19 @@
}
/**
+ * Sets user min aspect ratio override value
+ * @see PackageManager.UserMinAspectRatio
+ */
+ public @NonNull PackageUserStateImpl setMinAspectRatio(
+ @PackageManager.UserMinAspectRatio int value) {
+ mMinAspectRatio = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ PackageManager.UserMinAspectRatio.class, null, mMinAspectRatio);
+ onChanged();
+ return this;
+ }
+
+ /**
* Suspending package to suspend params
*/
public @NonNull PackageUserStateImpl setSuspendParams(
@@ -679,6 +696,11 @@
return mSplashScreenTheme;
}
+ @DataClass.Generated.Member
+ public @PackageManager.UserMinAspectRatio int getMinAspectRatio() {
+ return mMinAspectRatio;
+ }
+
/**
* Suspending package to suspend params
*/
@@ -766,6 +788,7 @@
&& Objects.equals(mOverlayPaths, that.mOverlayPaths)
&& Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths)
&& Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme)
+ && mMinAspectRatio == that.mMinAspectRatio
&& Objects.equals(mSuspendParams, that.mSuspendParams)
&& Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap)
&& mFirstInstallTimeMillis == that.mFirstInstallTimeMillis
@@ -798,6 +821,7 @@
_hash = 31 * _hash + Objects.hashCode(mOverlayPaths);
_hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths);
_hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme);
+ _hash = 31 * _hash + mMinAspectRatio;
_hash = 31 * _hash + Objects.hashCode(mSuspendParams);
_hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap);
_hash = 31 * _hash + Long.hashCode(mFirstInstallTimeMillis);
@@ -807,10 +831,10 @@
}
@DataClass.Generated(
- time = 1686952839807L,
+ time = 1687938397579L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
- inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+ inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 8125b0f..8430cf7 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -439,6 +439,16 @@
}
return null;
}
+
+ @NonNull
+ @Override
+ public PackageUserStateWrite setMinAspectRatio(
+ @PackageManager.UserMinAspectRatio int aspectRatio) {
+ if (mUserState != null) {
+ mUserState.setMinAspectRatio(aspectRatio);
+ }
+ return this;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
index 11d6d97..0c6c672 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
@@ -22,6 +22,7 @@
import android.content.pm.PackageManager;
import android.content.pm.overlay.OverlayPaths;
+import com.android.server.pm.pkg.PackageUserStateImpl;
import com.android.server.pm.pkg.SuspendParams;
public interface PackageUserStateWrite {
@@ -68,4 +69,8 @@
@NonNull
PackageUserStateWrite setComponentLabelIcon(@NonNull ComponentName componentName,
@Nullable String nonLocalizedLabel, @Nullable Integer icon);
+
+ /** @see PackageUserStateImpl#setMinAspectRatio(int) */
+ @NonNull
+ PackageUserStateWrite setMinAspectRatio(@PackageManager.UserMinAspectRatio int aspectRatio);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index a792b9c..279a480 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -91,6 +91,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityManagerInternal;
@@ -632,6 +633,8 @@
SettingsObserver mSettingsObserver;
ModifierShortcutManager mModifierShortcutManager;
+ /** Currently fully consumed key codes per device */
+ private final SparseArray<Set<Integer>> mConsumedKeysForDevice = new SparseArray<>();
PowerManager.WakeLock mBroadcastWakeLock;
PowerManager.WakeLock mPowerKeyWakeLock;
boolean mHavePendingMediaKeyRepeatWithWakeLock;
@@ -708,8 +711,11 @@
finishKeyguardDrawn();
break;
case MSG_WINDOW_MANAGER_DRAWN_COMPLETE:
- if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mWindowManagerDrawComplete");
- finishWindowsDrawn(msg.arg1);
+ final int displayId = msg.arg1;
+ if (DEBUG_WAKEUP) Slog.w(TAG, "All windows drawn on display " + displayId);
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER,
+ TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, displayId /* cookie */);
+ finishWindowsDrawn(displayId);
break;
case MSG_HIDE_BOOT_MESSAGE:
handleHideBootMessage();
@@ -1814,7 +1820,7 @@
mDisplayId = displayId;
}
- int handleHomeButton(IBinder focusedToken, KeyEvent event) {
+ boolean handleHomeButton(IBinder focusedToken, KeyEvent event) {
final boolean keyguardOn = keyguardOn();
final int repeatCount = event.getRepeatCount();
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
@@ -1835,12 +1841,12 @@
mHomePressed = false;
if (mHomeConsumed) {
mHomeConsumed = false;
- return -1;
+ return true;
}
if (canceled) {
Log.i(TAG, "Ignoring HOME; event canceled.");
- return -1;
+ return true;
}
// Delay handling home if a double-tap is possible.
@@ -1852,13 +1858,13 @@
mHomeDoubleTapPending = true;
mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
ViewConfiguration.getDoubleTapTimeout());
- return -1;
+ return true;
}
}
// Post to main thread to avoid blocking input pipeline.
mHandler.post(() -> handleShortPressOnHome(mDisplayId));
- return -1;
+ return true;
}
final KeyInterceptionInfo info =
@@ -1870,12 +1876,12 @@
|| (info.layoutParamsType == TYPE_NOTIFICATION_SHADE
&& isKeyguardShowing())) {
// the "app" is keyguard, so give it the key
- return 0;
+ return false;
}
for (int t : WINDOW_TYPES_WHERE_HOME_DOESNT_WORK) {
if (info.layoutParamsType == t) {
// don't do anything, but also don't pass it to the app
- return -1;
+ return true;
}
}
}
@@ -1900,7 +1906,7 @@
event.getEventTime()));
}
}
- return -1;
+ return true;
}
private void handleDoubleTapOnHome() {
@@ -2946,24 +2952,21 @@
@Override
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
int policyFlags) {
- final boolean keyguardOn = keyguardOn();
final int keyCode = event.getKeyCode();
- final int repeatCount = event.getRepeatCount();
- final int metaState = event.getMetaState();
final int flags = event.getFlags();
- final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
- final boolean canceled = event.isCanceled();
- final int displayId = event.getDisplayId();
- final long key_consumed = -1;
- final long key_not_consumed = 0;
+ final long keyConsumed = -1;
+ final long keyNotConsumed = 0;
+ final int deviceId = event.getDeviceId();
if (DEBUG_INPUT) {
- Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
- + repeatCount + " keyguardOn=" + keyguardOn + " canceled=" + canceled);
+ Log.d(TAG,
+ "interceptKeyTi keyCode=" + keyCode + " action=" + event.getAction()
+ + " repeatCount=" + event.getRepeatCount() + " keyguardOn="
+ + keyguardOn() + " canceled=" + event.isCanceled());
}
if (mKeyCombinationManager.isKeyConsumed(event)) {
- return key_consumed;
+ return keyConsumed;
}
if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
@@ -2974,8 +2977,54 @@
}
}
- // Cancel any pending meta actions if we see any other keys being pressed between the down
- // of the meta key and its corresponding up.
+ Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId);
+ if (consumedKeys == null) {
+ consumedKeys = new HashSet<>();
+ mConsumedKeysForDevice.put(deviceId, consumedKeys);
+ }
+
+ if (interceptSystemKeysAndShortcuts(focusedToken, event)
+ && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+ consumedKeys.add(keyCode);
+ return keyConsumed;
+ }
+
+ boolean needToConsumeKey = consumedKeys.contains(keyCode);
+ if (event.getAction() == KeyEvent.ACTION_UP || event.isCanceled()) {
+ consumedKeys.remove(keyCode);
+ if (consumedKeys.isEmpty()) {
+ mConsumedKeysForDevice.remove(deviceId);
+ }
+ }
+
+ return needToConsumeKey ? keyConsumed : keyNotConsumed;
+ }
+
+ // You can only start consuming the key gesture if ACTION_DOWN and repeat count
+ // is 0. If you start intercepting the key halfway, then key will not be consumed
+ // and will be sent to apps for processing too.
+ // e.g. If a certain combination is only handled on ACTION_UP (i.e.
+ // interceptShortcut() returns true only for ACTION_UP), then since we already
+ // sent the ACTION_DOWN events to the application, we MUST also send the
+ // ACTION_UP to the application.
+ // So, to ensure that your intercept logic works properly, and we don't send any
+ // conflicting events to application, make sure to consume the event on
+ // ACTION_DOWN even if you want to do something on ACTION_UP. This is essential
+ // to maintain event parity and to not have incomplete key gestures.
+ @SuppressLint("MissingPermission")
+ private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) {
+ final boolean keyguardOn = keyguardOn();
+ final int keyCode = event.getKeyCode();
+ final int repeatCount = event.getRepeatCount();
+ final int metaState = event.getMetaState();
+ final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+ final boolean canceled = event.isCanceled();
+ final int displayId = event.getDisplayId();
+ final int deviceId = event.getDeviceId();
+ final boolean firstDown = down && repeatCount == 0;
+
+ // Cancel any pending meta actions if we see any other keys being pressed between the
+ // down of the meta key and its corresponding up.
if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) {
mPendingMetaAction = false;
}
@@ -2989,50 +3038,49 @@
dismissKeyboardShortcutsMenu();
mPendingMetaAction = false;
mPendingCapsLockToggle = false;
- return key_consumed;
+ return true;
}
}
- switch(keyCode) {
+ switch (keyCode) {
case KeyEvent.KEYCODE_HOME:
- logKeyboardSystemsEvent(event, FrameworkStatsLog
- .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME);
+ logKeyboardSystemsEvent(event,
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME);
return handleHomeShortcuts(displayId, focusedToken, event);
case KeyEvent.KEYCODE_MENU:
// Hijack modified menu keys for debugging features
final int chordBug = KeyEvent.META_SHIFT_ON;
- if (down && repeatCount == 0) {
- if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) {
- Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
- mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
- null, null, null, 0, null, null);
- return key_consumed;
- }
+ if (mEnableShiftMenuBugReports && firstDown
+ && (metaState & chordBug) == chordBug) {
+ Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
+ mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
+ null, null, null, 0, null, null);
+ return true;
}
break;
case KeyEvent.KEYCODE_RECENT_APPS:
- if (down && repeatCount == 0) {
+ if (firstDown) {
showRecentApps(false /* triggeredFromAltTab */);
- logKeyboardSystemsEvent(event, FrameworkStatsLog
- .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS);
+ logKeyboardSystemsEvent(event,
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS);
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_APP_SWITCH:
if (!keyguardOn) {
- if (down && repeatCount == 0) {
+ if (firstDown) {
preloadRecentApps();
} else if (!down) {
toggleRecentApps();
}
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_A:
- if (down && event.isMetaPressed()) {
+ if (firstDown && event.isMetaPressed()) {
launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
- event.getDeviceId(),
- event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN);
- return key_consumed;
+ deviceId, event.getEventTime(),
+ AssistUtils.INVOCATION_TYPE_UNKNOWN);
+ return true;
}
break;
case KeyEvent.KEYCODE_H:
@@ -3042,73 +3090,73 @@
}
break;
case KeyEvent.KEYCODE_I:
- if (down && event.isMetaPressed()) {
+ if (firstDown && event.isMetaPressed()) {
showSystemSettings();
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_L:
- if (down && event.isMetaPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed()) {
lockNow(null /* options */);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_N:
- if (down && event.isMetaPressed()) {
+ if (firstDown && event.isMetaPressed()) {
if (event.isCtrlPressed()) {
sendSystemKeyToStatusBarAsync(event);
} else {
toggleNotificationPanel();
}
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_S:
- if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_T:
- if (down && event.isMetaPressed()) {
+ if (firstDown && event.isMetaPressed()) {
toggleTaskbar();
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_DPAD_UP:
- if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
if (statusbar != null) {
statusbar.goToFullscreenFromSplit();
+ return true;
}
- return key_consumed;
}
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
- if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
enterStageSplitFromRunningApp(true /* leftOrTop */);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
enterStageSplitFromRunningApp(false /* leftOrTop */);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_SLASH:
- if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) {
+ if (firstDown && event.isMetaPressed() && !keyguardOn) {
toggleKeyboardShortcutsMenu(event.getDeviceId());
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_ASSIST:
Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing");
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_VOICE_ASSIST:
Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in"
+ " interceptKeyBeforeQueueing");
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_VIDEO_APP_1:
case KeyEvent.KEYCODE_VIDEO_APP_2:
case KeyEvent.KEYCODE_VIDEO_APP_3:
@@ -3126,7 +3174,7 @@
case KeyEvent.KEYCODE_DEMO_APP_3:
case KeyEvent.KEYCODE_DEMO_APP_4:
Slog.wtf(TAG, "KEYCODE_APP_X should be handled in interceptKeyBeforeQueueing");
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_BRIGHTNESS_UP:
case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
if (down) {
@@ -3166,20 +3214,20 @@
startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
UserHandle.CURRENT_OR_SELF);
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
if (down) {
mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId());
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
if (down) {
mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId());
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
// TODO: Add logic
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE:
@@ -3187,7 +3235,7 @@
// On TVs or when the configuration is enabled, volume keys never
// go to the foreground app.
dispatchDirectAudioEvent(event);
- return key_consumed;
+ return true;
}
// If the device is in VR mode and keys are "internal" (e.g. on the side of the
@@ -3196,26 +3244,23 @@
if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) {
final InputDevice d = event.getDevice();
if (d != null && !d.isExternal()) {
- return key_consumed;
+ return true;
}
}
break;
case KeyEvent.KEYCODE_TAB:
- if (down && event.isMetaPressed()) {
- if (!keyguardOn && isUserSetupComplete()) {
+ if (firstDown && !keyguardOn && isUserSetupComplete()) {
+ if (event.isMetaPressed()) {
showRecentApps(false);
- return key_consumed;
- }
- } else if (down && repeatCount == 0) {
- // Display task switcher for ALT-TAB.
- if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
+ return true;
+ } else if (mRecentAppsHeldModifiers == 0) {
final int shiftlessModifiers =
event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
if (KeyEvent.metaStateHasModifiers(
shiftlessModifiers, KeyEvent.META_ALT_ON)) {
mRecentAppsHeldModifiers = shiftlessModifiers;
showRecentApps(true);
- return key_consumed;
+ return true;
}
}
}
@@ -3227,18 +3272,18 @@
msg.setAsynchronous(true);
msg.sendToTarget();
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_NOTIFICATION:
if (!down) {
toggleNotificationPanel();
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_SEARCH:
- if (down && repeatCount == 0 && !keyguardOn()) {
- switch(mSearchKeyBehavior) {
+ if (firstDown && !keyguardOn) {
+ switch (mSearchKeyBehavior) {
case SEARCH_BEHAVIOR_TARGET_ACTIVITY: {
launchTargetSearchActivity();
- return key_consumed;
+ return true;
}
case SEARCH_BEHAVIOR_DEFAULT_SEARCH:
default:
@@ -3247,21 +3292,18 @@
}
break;
case KeyEvent.KEYCODE_LANGUAGE_SWITCH:
- if (down && repeatCount == 0) {
+ if (firstDown) {
int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
sendSwitchKeyboardLayout(event, direction);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_SPACE:
// Handle keyboard layout switching. (META + SPACE)
- if ((metaState & KeyEvent.META_META_MASK) == 0) {
- return key_not_consumed;
- }
- if (down && repeatCount == 0) {
+ if (firstDown && event.isMetaPressed()) {
int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
sendSwitchKeyboardLayout(event, direction);
- return key_consumed;
+ return true;
}
break;
case KeyEvent.KEYCODE_META_LEFT:
@@ -3286,7 +3328,7 @@
mPendingMetaAction = false;
}
}
- return key_consumed;
+ return true;
case KeyEvent.KEYCODE_ALT_LEFT:
case KeyEvent.KEYCODE_ALT_RIGHT:
if (down) {
@@ -3302,14 +3344,14 @@
&& (metaState & mRecentAppsHeldModifiers) == 0) {
mRecentAppsHeldModifiers = 0;
hideRecentApps(true, false);
- return key_consumed;
+ return true;
}
// Toggle Caps Lock on META-ALT.
if (mPendingCapsLockToggle) {
mInputManagerInternal.toggleCapsLock(event.getDeviceId());
mPendingCapsLockToggle = false;
- return key_consumed;
+ return true;
}
}
break;
@@ -3319,24 +3361,18 @@
case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL:
Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
+ " interceptKeyBeforeQueueing");
- return key_consumed;
+ return true;
}
-
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
- return key_consumed;
+ return true;
}
// Reserve all the META modifier combos for system behavior
- if ((metaState & KeyEvent.META_META_ON) != 0) {
- return key_consumed;
- }
-
- // Let the application handle the key.
- return key_not_consumed;
+ return (metaState & KeyEvent.META_META_ON) != 0;
}
- private int handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
+ private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
// First we always handle the home key here, so applications
// can never break it, although if keyguard is on, we do let
// it handle it, because that gives us the correct 5 second
@@ -3618,19 +3654,17 @@
}
@Override
- public void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition) {
- if (mKeyguardDelegate != null && waitAppTransition) {
+ public void onKeyguardOccludedChangedLw(boolean occluded) {
+ if (mKeyguardDelegate != null) {
mPendingKeyguardOccluded = occluded;
mKeyguardOccludedChanged = true;
- } else {
- setKeyguardOccludedLw(occluded);
}
}
@Override
public int applyKeyguardOcclusionChange() {
if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded commit occluded="
- + mPendingKeyguardOccluded);
+ + mPendingKeyguardOccluded + " changed=" + mKeyguardOccludedChanged);
// TODO(b/276433230): Explicitly save before/after for occlude state in each
// Transition so we don't need to update SysUI every time.
@@ -5046,15 +5080,10 @@
// ... eventually calls finishWindowsDrawn which will finalize our screen turn on
// as well as enabling the orientation change logic/sensor.
Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
- TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
- mWindowManagerInternal.waitForAllWindowsDrawn(() -> {
- if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for every display");
- mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE,
- INVALID_DISPLAY, 0));
-
- Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER,
- TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
- }, WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY);
+ TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, INVALID_DISPLAY /* cookie */);
+ mWindowManagerInternal.waitForAllWindowsDrawn(mHandler.obtainMessage(
+ MSG_WINDOW_MANAGER_DRAWN_COMPLETE, INVALID_DISPLAY, 0),
+ WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY);
}
// Called on the DisplayManager's DisplayPowerController thread.
@@ -5134,15 +5163,10 @@
mScreenOnListeners.put(displayId, screenOnListener);
Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
- TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
- mWindowManagerInternal.waitForAllWindowsDrawn(() -> {
- if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display: " + displayId);
- mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE,
- displayId, 0));
-
- Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER,
- TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
- }, WAITING_FOR_DRAWN_TIMEOUT, displayId);
+ TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, displayId /* cookie */);
+ mWindowManagerInternal.waitForAllWindowsDrawn(mHandler.obtainMessage(
+ MSG_WINDOW_MANAGER_DRAWN_COMPLETE, displayId, 0),
+ WAITING_FOR_DRAWN_TIMEOUT, displayId);
}
}
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 9c3b38a..b999bbb3 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -314,7 +314,9 @@
if (eventTime < mLastDownTime + mActiveRule.getVeryLongPressTimeoutMs()) {
mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
} else {
- mHandledByLongPress = mActiveRule.supportVeryLongPress();
+ // If long press or very long press (~3.5s) had been handled, we should skip the
+ // short press behavior.
+ mHandledByLongPress |= mActiveRule.supportVeryLongPress();
}
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 887f946..03a7bd3 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -169,7 +169,7 @@
*
* @param occluded Whether Keyguard is currently occluded or not.
*/
- void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition);
+ void onKeyguardOccludedChangedLw(boolean occluded);
/**
* Commit any queued changes to keyguard occlude status that had been deferred during the
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 4a57592a..27329e2 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -14613,17 +14613,13 @@
// Inform StatsLog of setBatteryState changes.
private void reportChangesToStatsLog(final int status, final int plugType, final int level) {
- if (!mHaveBatteryLevel) {
- return;
- }
-
- if (mBatteryStatus != status) {
+ if (!mHaveBatteryLevel || mBatteryStatus != status) {
FrameworkStatsLog.write(FrameworkStatsLog.CHARGING_STATE_CHANGED, status);
}
- if (mBatteryPlugType != plugType) {
+ if (!mHaveBatteryLevel || mBatteryPlugType != plugType) {
FrameworkStatsLog.write(FrameworkStatsLog.PLUGGED_STATE_CHANGED, plugType);
}
- if (mBatteryLevel != level) {
+ if (!mHaveBatteryLevel || mBatteryLevel != level) {
FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_LEVEL_CHANGED, level);
}
}
diff --git a/services/core/java/com/android/server/utils/AlarmQueue.java b/services/core/java/com/android/server/utils/AlarmQueue.java
index 09ba195..83605ae 100644
--- a/services/core/java/com/android/server/utils/AlarmQueue.java
+++ b/services/core/java/com/android/server/utils/AlarmQueue.java
@@ -151,6 +151,10 @@
@GuardedBy("mLock")
@ElapsedRealtimeLong
private long mTriggerTimeElapsed = NOT_SCHEDULED;
+ /** The last time an alarm went off (ie. the last time {@link #onAlarm()} was called}). */
+ @GuardedBy("mLock")
+ @ElapsedRealtimeLong
+ private long mLastFireTimeElapsed;
/**
* @param alarmTag The tag to use when scheduling the alarm with AlarmManager.
@@ -284,7 +288,7 @@
/** Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue after now. */
@GuardedBy("mLock")
private void setNextAlarmLocked() {
- setNextAlarmLocked(mInjector.getElapsedRealtime());
+ setNextAlarmLocked(mLastFireTimeElapsed + mMinTimeBetweenAlarmsMs);
}
/**
@@ -334,6 +338,7 @@
final ArraySet<K> expired = new ArraySet<>();
synchronized (mLock) {
final long nowElapsed = mInjector.getElapsedRealtime();
+ mLastFireTimeElapsed = nowElapsed;
while (mAlarmPriorityQueue.size() > 0) {
final Pair<K, Long> alarm = mAlarmPriorityQueue.peek();
if (alarm.second <= nowElapsed) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 9b2cdd7..ee7dc50 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3113,7 +3113,10 @@
if (which == FLAG_SYSTEM && systemIsStatic && systemIsBoth) {
Slog.i(TAG, "Migrating current wallpaper to be lock-only before"
+ " updating system wallpaper");
- migrateStaticSystemToLockWallpaperLocked(userId);
+ if (!migrateStaticSystemToLockWallpaperLocked(userId)
+ && !isLockscreenLiveWallpaperEnabled()) {
+ which |= FLAG_LOCK;
+ }
}
wallpaper = getWallpaperSafeLocked(userId, which);
@@ -3141,13 +3144,13 @@
}
}
- private void migrateStaticSystemToLockWallpaperLocked(int userId) {
+ private boolean migrateStaticSystemToLockWallpaperLocked(int userId) {
WallpaperData sysWP = mWallpaperMap.get(userId);
if (sysWP == null) {
if (DEBUG) {
Slog.i(TAG, "No system wallpaper? Not tracking for lock-only");
}
- return;
+ return true;
}
// We know a-priori that there is no lock-only wallpaper currently
@@ -3174,9 +3177,12 @@
SELinux.restorecon(lockWP.getWallpaperFile());
mLastLockWallpaper = lockWP;
}
+ return true;
} catch (ErrnoException e) {
- Slog.e(TAG, "Can't migrate system wallpaper: " + e.getMessage());
+ // can happen when migrating default wallpaper (which is not stored in wallpaperFile)
+ Slog.w(TAG, "Couldn't migrate system wallpaper: " + e.getMessage());
clearWallpaperBitmaps(lockWP);
+ return false;
}
}
@@ -3388,7 +3394,9 @@
// therefore it's a shared system+lock image that we need to migrate.
Slog.i(TAG, "Migrating current wallpaper to be lock-only before"
+ "updating system wallpaper");
- migrateStaticSystemToLockWallpaperLocked(userId);
+ if (!migrateStaticSystemToLockWallpaperLocked(userId)) {
+ which |= FLAG_LOCK;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 32f1f42..a2547fd 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -23,6 +23,7 @@
import android.app.AppProtoEnums;
import android.app.BackgroundStartPrivileges;
import android.app.IActivityManager;
+import android.app.IAppTask;
import android.app.IApplicationThread;
import android.app.ITaskStackListener;
import android.app.ProfilerInfo;
@@ -308,6 +309,12 @@
public abstract void notifyActiveDreamChanged(@Nullable ComponentName activeDreamComponent);
/**
+ * Starts a dream activity in the DreamService's process.
+ */
+ public abstract IAppTask startDreamActivity(@NonNull Intent intent, int callingUid,
+ int callingPid);
+
+ /**
* Set a uid that is allowed to bypass stopped app switches, launching an app
* whenever it wants.
*
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 10ff3a3..78da5de 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1467,23 +1467,8 @@
return false;
}
- private void enforceCallerIsDream(String callerPackageName) {
- final long origId = Binder.clearCallingIdentity();
- try {
- if (!canLaunchDreamActivity(callerPackageName)) {
- throw new SecurityException("The dream activity can be started only when the device"
- + " is dreaming and only by the active dream package.");
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
-
- @Override
- public boolean startDreamActivity(@NonNull Intent intent) {
- assertPackageMatchesCallingUid(intent.getPackage());
- enforceCallerIsDream(intent.getPackage());
-
+ private IAppTask startDreamActivityInternal(@NonNull Intent intent, int callingUid,
+ int callingPid) {
final ActivityInfo a = new ActivityInfo();
a.theme = com.android.internal.R.style.Theme_Dream;
a.exported = true;
@@ -1501,7 +1486,7 @@
options.setLaunchActivityType(ACTIVITY_TYPE_DREAM);
synchronized (mGlobalLock) {
- final WindowProcessController process = mProcessMap.getProcess(Binder.getCallingPid());
+ final WindowProcessController process = mProcessMap.getProcess(callingPid);
a.packageName = process.mInfo.packageName;
a.applicationInfo = process.mInfo;
@@ -1509,26 +1494,25 @@
a.uiOptions = process.mInfo.uiOptions;
a.taskAffinity = "android:" + a.packageName + "/dream";
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- final long origId = Binder.clearCallingIdentity();
- try {
- getActivityStartController().obtainStarter(intent, "dream")
- .setCallingUid(callingUid)
- .setCallingPid(callingPid)
- .setCallingPackage(intent.getPackage())
- .setActivityInfo(a)
- .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options))
- // To start the dream from background, we need to start it from a persistent
- // system process. Here we set the real calling uid to the system server uid
- .setRealCallingUid(Binder.getCallingUid())
- .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
- .execute();
- return true;
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
+ final ActivityRecord[] outActivity = new ActivityRecord[1];
+ getActivityStartController().obtainStarter(intent, "dream")
+ .setCallingUid(callingUid)
+ .setCallingPid(callingPid)
+ .setCallingPackage(intent.getPackage())
+ .setActivityInfo(a)
+ .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options))
+ .setOutActivity(outActivity)
+ // To start the dream from background, we need to start it from a persistent
+ // system process. Here we set the real calling uid to the system server uid
+ .setRealCallingUid(Binder.getCallingUid())
+ .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
+ .execute();
+
+ final ActivityRecord started = outActivity[0];
+ final IAppTask appTask = started == null ? null :
+ new AppTaskImpl(this, started.getTask().mTaskId, callingUid);
+ return appTask;
}
}
@@ -5926,6 +5910,11 @@
}
@Override
+ public IAppTask startDreamActivity(@NonNull Intent intent, int callingUid, int callingPid) {
+ return startDreamActivityInternal(intent, callingUid, callingPid);
+ }
+
+ @Override
public void setAllowAppSwitches(@NonNull String type, int uid, int userId) {
if (!mAmInternal.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) {
return;
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 0a47fe0..20595ea 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -128,6 +128,21 @@
}
}
+ /** Notifies that the pointer location configuration has changed. */
+ @Override
+ public void notifyPointerLocationChanged(boolean pointerLocationEnabled) {
+ if (mService.mPointerLocationEnabled == pointerLocationEnabled) {
+ return;
+ }
+
+ synchronized (mService.mGlobalLock) {
+ mService.mPointerLocationEnabled = pointerLocationEnabled;
+ mService.mRoot.forAllDisplayPolicies(
+ p -> p.setPointerLocationEnabled(mService.mPointerLocationEnabled)
+ );
+ }
+ }
+
/** Notifies that the lid switch changed state. */
@Override
public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 4620266..835c92d 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -21,12 +21,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
-import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
-import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
-import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
import static android.view.InsetsSource.ID_IME;
-import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
@@ -39,16 +34,13 @@
import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.res.Resources;
+import android.os.Handler;
+import android.os.IBinder;
import android.util.SparseArray;
-import android.view.InsetsAnimationControlCallbacks;
-import android.view.InsetsAnimationControlImpl;
-import android.view.InsetsAnimationControlRunner;
import android.view.InsetsController;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
-import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InternalInsetsAnimationController;
import android.view.SurfaceControl;
import android.view.SyncRtSurfaceTransactionApplier;
import android.view.WindowInsets;
@@ -56,15 +48,15 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsAnimation.Bounds;
-import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.DisplayThread;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.PrintWriter;
+import java.util.List;
/**
* Policy that implements who gets control over the windows generating insets.
@@ -79,48 +71,19 @@
private final DisplayContent mDisplayContent;
private final DisplayPolicy mPolicy;
- /** For resetting visibilities of insets sources. */
- private final InsetsControlTarget mDummyControlTarget = new InsetsControlTarget() {
+ /** Used to show system bars transiently. This won't affect the layout. */
+ private final InsetsControlTarget mTransientControlTarget;
- @Override
- public void notifyInsetsControlChanged() {
- boolean hasLeash = false;
- final InsetsSourceControl[] controls =
- mStateController.getControlsForDispatch(this);
- if (controls == null) {
- return;
- }
- for (InsetsSourceControl control : controls) {
- if (isTransient(control.getType())) {
- // The visibilities of transient bars will be handled with animations.
- continue;
- }
- final SurfaceControl leash = control.getLeash();
- if (leash != null) {
- hasLeash = true;
-
- // We use alpha to control the visibility here which aligns the logic at
- // SurfaceAnimator.createAnimationLeash
- final boolean visible =
- (control.getType() & WindowInsets.Type.defaultVisible()) != 0;
- mDisplayContent.getPendingTransaction().setAlpha(leash, visible ? 1f : 0f);
- }
- }
- if (hasLeash) {
- mDisplayContent.scheduleAnimation();
- }
- }
- };
+ /** Used to show system bars permanently. This will affect the layout. */
+ private final InsetsControlTarget mPermanentControlTarget;
private WindowState mFocusedWin;
private final BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
private final BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR);
private @InsetsType int mShowingTransientTypes;
private @InsetsType int mForcedShowingTypes;
- private boolean mAnimatingShown;
private final boolean mHideNavBarForKeyboard;
- private final float[] mTmpFloat9 = new float[9];
InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) {
mStateController = stateController;
@@ -128,6 +91,10 @@
mPolicy = displayContent.getDisplayPolicy();
final Resources r = mPolicy.getContext().getResources();
mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard);
+ mTransientControlTarget = new ControlTarget(
+ stateController, displayContent.mWmService.mH, "TransientControlTarget");
+ mPermanentControlTarget = new ControlTarget(
+ stateController, displayContent.mWmService.mH, "PermanentControlTarget");
}
/** Updates the target which can control system bars. */
@@ -144,13 +111,13 @@
final WindowState topApp = mPolicy.getTopFullscreenOpaqueWindow();
mStateController.onBarControlTargetChanged(
statusControlTarget,
- statusControlTarget == mDummyControlTarget
+ statusControlTarget == mTransientControlTarget
? getStatusControlTarget(focusedWin, true /* fake */)
: statusControlTarget == notificationShade
? getStatusControlTarget(topApp, true /* fake */)
: null,
navControlTarget,
- navControlTarget == mDummyControlTarget
+ navControlTarget == mTransientControlTarget
? getNavControlTarget(focusedWin, true /* fake */)
: navControlTarget == notificationShade
? getNavControlTarget(topApp, true /* fake */)
@@ -200,19 +167,19 @@
mFocusedWin,
(showingTransientTypes & (Type.statusBars() | Type.navigationBars())) != 0,
isGestureOnSystemBar);
-
- // The leashes can be created while updating bar control target. The surface transaction
- // of the new leashes might not be applied yet. The callback posted here ensures we can
- // get the valid leashes because the surface transaction will be applied in the next
- // animation frame which will be triggered if a new leash is created.
- mDisplayContent.mWmService.mAnimator.getChoreographer().postFrameCallback(time -> {
- synchronized (mDisplayContent.mWmService.mGlobalLock) {
- startAnimation(true /* show */, null /* callback */);
- }
- });
}
}
+ @VisibleForTesting
+ InsetsControlTarget getTransientControlTarget() {
+ return mTransientControlTarget;
+ }
+
+ @VisibleForTesting
+ InsetsControlTarget getPermanentControlTarget() {
+ return mPermanentControlTarget;
+ }
+
void hideTransient() {
if (mShowingTransientTypes == 0) {
return;
@@ -223,23 +190,8 @@
/* areVisible= */ false,
/* wereRevealedFromSwipeOnSystemBar= */ false);
- startAnimation(false /* show */, () -> {
- synchronized (mDisplayContent.mWmService.mGlobalLock) {
- final SparseArray<InsetsSourceProvider> providers =
- mStateController.getSourceProviders();
- for (int i = providers.size() - 1; i >= 0; i--) {
- final InsetsSourceProvider provider = providers.valueAt(i);
- if (!isTransient(provider.getSource().getType())) {
- continue;
- }
- // We are about to clear mShowingTransientTypes, we don't want the transient bar
- // can cause insets on the client. Restore the client visibility.
- provider.setClientVisible(false);
- }
- mShowingTransientTypes = 0;
- updateBarControlTarget(mFocusedWin);
- }
- });
+ mShowingTransientTypes = 0;
+ updateBarControlTarget(mFocusedWin);
}
boolean isTransient(@InsetsType int type) {
@@ -502,7 +454,7 @@
private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin,
boolean fake) {
if (!fake && isTransient(Type.statusBars())) {
- return mDummyControlTarget;
+ return mTransientControlTarget;
}
final WindowState notificationShade = mPolicy.getNotificationShade();
if (focusedWin == notificationShade) {
@@ -519,13 +471,13 @@
if (areTypesForciblyShowing(Type.statusBars())) {
// Status bar is forcibly shown. We don't want the client to control the status bar, and
// we will dispatch the real visibility of status bar to the client.
- return null;
+ return mPermanentControlTarget;
}
if (forceShowsStatusBarTransiently() && !fake) {
// Status bar is forcibly shown transiently, and its new visibility won't be
// dispatched to the client so that we can keep the layout stable. We will dispatch the
// fake control to the client, so that it can re-show the bar during this scenario.
- return mDummyControlTarget;
+ return mTransientControlTarget;
}
if (!canBeTopFullscreenOpaqueWindow(focusedWin)
&& mPolicy.topAppHidesSystemBar(Type.statusBars())
@@ -556,7 +508,7 @@
return null;
}
if (!fake && isTransient(Type.navigationBars())) {
- return mDummyControlTarget;
+ return mTransientControlTarget;
}
if (focusedWin == mPolicy.getNotificationShade()) {
// Notification shade has control anyways, no reason to force anything.
@@ -579,13 +531,13 @@
if (areTypesForciblyShowing(Type.navigationBars())) {
// Navigation bar is forcibly shown. We don't want the client to control the navigation
// bar, and we will dispatch the real visibility of navigation bar to the client.
- return null;
+ return mPermanentControlTarget;
}
if (forceShowsNavigationBarTransiently() && !fake) {
// Navigation bar is forcibly shown transiently, and its new visibility won't be
// dispatched to the client so that we can keep the layout stable. We will dispatch the
// fake control to the client, so that it can re-show the bar during this scenario.
- return mDummyControlTarget;
+ return mTransientControlTarget;
}
final WindowState notificationShade = mPolicy.getNotificationShade();
if (!canBeTopFullscreenOpaqueWindow(focusedWin)
@@ -662,34 +614,6 @@
&& (win.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
}
- @VisibleForTesting
- void startAnimation(boolean show, Runnable callback) {
- @InsetsType int typesReady = 0;
- final SparseArray<InsetsSourceControl> controlsReady = new SparseArray<>();
- final InsetsSourceControl[] controls =
- mStateController.getControlsForDispatch(mDummyControlTarget);
- if (controls == null) {
- if (callback != null) {
- DisplayThread.getHandler().post(callback);
- }
- return;
- }
- for (InsetsSourceControl control : controls) {
- if (isTransient(control.getType()) && control.getLeash() != null) {
- typesReady |= control.getType();
- controlsReady.put(control.getId(), new InsetsSourceControl(control));
- }
- }
- controlAnimationUnchecked(typesReady, controlsReady, show, callback);
- }
-
- private void controlAnimationUnchecked(int typesReady,
- SparseArray<InsetsSourceControl> controls, boolean show, Runnable callback) {
- InsetsPolicyAnimationControlListener listener =
- new InsetsPolicyAnimationControlListener(show, callback, typesReady);
- listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show);
- }
-
private void dispatchTransientSystemBarsVisibilityChanged(
@Nullable WindowState focusedWindow,
boolean areVisible,
@@ -760,105 +684,138 @@
}
}
- private class InsetsPolicyAnimationControlListener extends
- InsetsController.InternalAnimationControlListener {
- Runnable mFinishCallback;
- InsetsPolicyAnimationControlCallbacks mControlCallbacks;
+ private static class ControlTarget implements InsetsControlTarget {
- InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
- super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
- false /* disable */, 0 /* floatingImeBottomInsets */,
- null /* loggingListener */, null /* jankContext */);
- mFinishCallback = finishCallback;
- mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
+ private final InsetsState mState = new InsetsState();
+ private final InsetsController mInsetsController;
+ private final InsetsStateController mStateController;
+ private final String mName;
+
+ ControlTarget(InsetsStateController stateController, Handler handler, String name) {
+ mStateController = stateController;
+ mInsetsController = new InsetsController(new Host(handler, name));
+ mName = name;
}
@Override
- protected void onAnimationFinish() {
- super.onAnimationFinish();
- if (mFinishCallback != null) {
- DisplayThread.getHandler().post(mFinishCallback);
- }
+ public void notifyInsetsControlChanged() {
+ mState.set(mStateController.getRawInsetsState(), true /* copySources */);
+ mInsetsController.onStateChanged(mState);
+ mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this));
}
- private class InsetsPolicyAnimationControlCallbacks implements
- InsetsAnimationControlCallbacks {
- private InsetsAnimationControlImpl mAnimationControl = null;
- private InsetsPolicyAnimationControlListener mListener;
+ @Override
+ public String toString() {
+ return mName;
+ }
+ }
- InsetsPolicyAnimationControlCallbacks(InsetsPolicyAnimationControlListener listener) {
- mListener = listener;
+ private static class Host implements InsetsController.Host {
+
+ private final float[] mTmpFloat9 = new float[9];
+ private final Handler mHandler;
+ private final String mName;
+
+ Host(Handler handler, String name) {
+ mHandler = handler;
+ mName = name;
+ }
+
+ @Override
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ @Override
+ public void notifyInsetsChanged() { }
+
+ @Override
+ public void dispatchWindowInsetsAnimationPrepare(
+ @NonNull WindowInsetsAnimation animation) { }
+
+ @Override
+ public Bounds dispatchWindowInsetsAnimationStart(
+ @NonNull WindowInsetsAnimation animation,
+ @NonNull Bounds bounds) {
+ return bounds;
+ }
+
+ @Override
+ public WindowInsets dispatchWindowInsetsAnimationProgress(
+ @NonNull WindowInsets insets,
+ @NonNull List<WindowInsetsAnimation> runningAnimations) {
+ return insets;
+ }
+
+ @Override
+ public void dispatchWindowInsetsAnimationEnd(
+ @NonNull WindowInsetsAnimation animation) { }
+
+ @Override
+ public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... p) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (int i = p.length - 1; i >= 0; i--) {
+ SyncRtSurfaceTransactionApplier.applyParams(t, p[i], mTmpFloat9);
}
+ t.apply();
+ t.close();
+ }
- private void controlAnimationUnchecked(int typesReady,
- SparseArray<InsetsSourceControl> controls, boolean show) {
- if (typesReady == 0) {
- // nothing to animate.
- return;
- }
- mAnimatingShown = show;
+ @Override
+ public void updateRequestedVisibleTypes(int types) { }
- final InsetsState state = mFocusedWin.getInsetsState();
+ @Override
+ public boolean hasAnimationCallbacks() {
+ return false;
+ }
- // We are about to playing the default animation. Passing a null frame indicates
- // the controlled types should be animated regardless of the frame.
- mAnimationControl = new InsetsAnimationControlImpl(controls,
- null /* frame */, state, mListener, typesReady, this,
- mListener.getDurationMs(), getInsetsInterpolator(),
- show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show
- ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
- : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
- null /* translator */, null /* statsToken */);
- SurfaceAnimationThread.getHandler().post(
- () -> mListener.onReady(mAnimationControl, typesReady));
- }
+ @Override
+ public void setSystemBarsAppearance(int appearance, int mask) { }
- /** Called on SurfaceAnimationThread without global WM lock held. */
- @Override
- public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) {
- if (mAnimationControl.applyChangeInsets(null /* outState */)) {
- mAnimationControl.finish(mAnimatingShown);
- }
- }
+ @Override
+ public int getSystemBarsAppearance() {
+ return 0;
+ }
- @Override
- public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) {
- // Nothing's needed here. Finish steps is handled in the listener
- // onAnimationFinished callback.
- }
+ @Override
+ public void setSystemBarsBehavior(int behavior) { }
- /** Called on SurfaceAnimationThread without global WM lock held. */
- @Override
- public void applySurfaceParams(
- final SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- for (int i = params.length - 1; i >= 0; i--) {
- SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i];
- applyParams(t, surfaceParams, mTmpFloat9);
- }
- t.apply();
- t.close();
- }
+ @Override
+ public int getSystemBarsBehavior() {
+ return BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+ }
- // Since we don't push applySurfaceParams to a Handler-queue we don't need
- // to push release in this case.
- @Override
- public void releaseSurfaceControlFromRt(SurfaceControl sc) {
- sc.release();
- }
+ @Override
+ public void releaseSurfaceControlFromRt(SurfaceControl surfaceControl) {
+ surfaceControl.release();
+ }
- @Override
- public <T extends InsetsAnimationControlRunner & InternalInsetsAnimationController>
- void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types,
- WindowInsetsAnimation animation,
- Bounds bounds) {
- }
+ @Override
+ public void addOnPreDrawRunnable(Runnable r) { }
- @Override
- public void reportPerceptible(int types, boolean perceptible) {
- // No-op for now - only client windows report perceptibility for now, with policy
- // controllers assumed to always be perceptible.
- }
+ @Override
+ public void postInsetsAnimationCallback(Runnable r) { }
+
+ @Override
+ public InputMethodManager getInputMethodManager() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String getRootViewTitle() {
+ return mName;
+ }
+
+ @Override
+ public int dipToPx(int dips) {
+ return 0;
+ }
+
+ @Nullable
+ @Override
+ public IBinder getWindowToken() {
+ return null;
}
}
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index ad9c3b2..83fd725 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -418,13 +418,17 @@
return;
}
- final boolean waitAppTransition = isKeyguardLocked(displayId);
- mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY),
- waitAppTransition);
- if (waitAppTransition) {
- mService.deferWindowLayout();
- try {
- if (isDisplayOccluded(DEFAULT_DISPLAY)) {
+ final TransitionController tc = mRootWindowContainer.mTransitionController;
+
+ final boolean occluded = isDisplayOccluded(displayId);
+ final boolean performTransition = isKeyguardLocked(displayId);
+ final boolean executeTransition = performTransition && !tc.isCollecting();
+
+ mWindowManager.mPolicy.onKeyguardOccludedChangedLw(occluded);
+ mService.deferWindowLayout();
+ try {
+ if (isKeyguardLocked(displayId)) {
+ if (occluded) {
mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
TRANSIT_KEYGUARD_OCCLUDE,
TRANSIT_FLAG_KEYGUARD_OCCLUDING,
@@ -434,11 +438,19 @@
TRANSIT_KEYGUARD_UNOCCLUDE,
TRANSIT_FLAG_KEYGUARD_UNOCCLUDING);
}
- updateKeyguardSleepToken(DEFAULT_DISPLAY);
- mWindowManager.executeAppTransition();
- } finally {
- mService.continueWindowLayout();
+ } else {
+ if (tc.inTransition()) {
+ tc.mStateValidators.add(mWindowManager.mPolicy::applyKeyguardOcclusionChange);
+ } else {
+ mWindowManager.mPolicy.applyKeyguardOcclusionChange();
+ }
}
+ updateKeyguardSleepToken(displayId);
+ if (performTransition && executeTransition) {
+ mWindowManager.executeAppTransition();
+ }
+ } finally {
+ mService.continueWindowLayout();
}
}
@@ -485,6 +497,9 @@
}
}
+ /**
+ * @return true if Keyguard is occluded or the device is dreaming.
+ */
boolean isDisplayOccluded(int displayId) {
return getDisplayState(displayId).mOccluded;
}
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index bf511adf0..2394da9 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -433,7 +433,7 @@
final byte[] data = saveParamsToXml();
final File launchParamFolder = getLaunchParamFolder(mUserId);
- if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdirs()) {
+ if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdir()) {
Slog.w(TAG, "Failed to create folder for " + mUserId);
return;
}
diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java
index 29c192c..f882b9b 100644
--- a/services/core/java/com/android/server/wm/TaskPersister.java
+++ b/services/core/java/com/android/server/wm/TaskPersister.java
@@ -509,7 +509,7 @@
private static boolean createParentDirectory(String filePath) {
File parentDir = new File(filePath).getParentFile();
- return parentDir.exists() || parentDir.mkdirs();
+ return parentDir.isDirectory() || parentDir.mkdir();
}
private static class TaskWriteQueueItem implements PersisterQueue.WriteQueueItem {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f2e0260..db3b267 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -27,6 +27,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
+import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -77,6 +78,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -613,14 +615,16 @@
}
}
if (mParticipants.contains(wc)) return;
- // Wallpaper is like in a static drawn state unless display may have changes, so exclude
- // the case to reduce transition latency waiting for the unchanged wallpaper to redraw.
- final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent))
- // Transient-hide may be hidden later, so no need to request redraw.
- && !isInTransientHide(wc);
- if (needSync) {
+ // Transient-hide may be hidden later, so no need to request redraw.
+ if (!isInTransientHide(wc)) {
mSyncEngine.addToSyncSet(mSyncId, wc);
}
+ if (wc.asWindowToken() != null && wc.asWindowToken().mRoundedCornerOverlay) {
+ // Only need to sync the transaction (SyncSet) without ChangeInfo because cutout and
+ // rounded corner overlay never need animations. Especially their surfaces may be put
+ // in root (null, see WindowToken#makeSurface()) that cannot reparent.
+ return;
+ }
ChangeInfo info = mChanges.get(wc);
if (info == null) {
info = new ChangeInfo(wc);
@@ -2667,7 +2671,7 @@
}
private void validateKeyguardOcclusion() {
- if ((mFlags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
+ if ((mFlags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
mController.mStateValidators.add(
mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange);
}
@@ -2696,6 +2700,26 @@
});
}
+ /**
+ * Returns {@code true} if the transition and the corresponding transaction should be applied
+ * on display thread. Currently, this only checks for display rotation change because the order
+ * of dispatching the new display info will be after requesting the windows to sync drawing.
+ * That avoids potential flickering of screen overlays (e.g. cutout, rounded corner). Also,
+ * because the display thread has a higher priority, it is faster to perform the configuration
+ * changes and window hierarchy traversal.
+ */
+ boolean shouldApplyOnDisplayThread() {
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ final DisplayContent dc = mParticipants.valueAt(i).asDisplayContent();
+ if (dc == null) continue;
+ final ChangeInfo changeInfo = mChanges.get(dc);
+ if (changeInfo != null && changeInfo.mRotation != dc.getRotation()) {
+ return Looper.myLooper() != mController.mAtm.mWindowManager.mH.getLooper();
+ }
+ }
+ return false;
+ }
+
/** Applies the new configuration for the changed displays. */
void applyDisplayChangeIfNeeded() {
for (int i = mParticipants.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index a539a48..79cb61b 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -588,15 +588,6 @@
/** Sets the sync method for the display change. */
private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
@NonNull Transition displayTransition, @NonNull DisplayContent displayContent) {
- final int startRotation = displayChange.getStartRotation();
- final int endRotation = displayChange.getEndRotation();
- if (startRotation != endRotation && (startRotation + endRotation) % 2 == 0) {
- // 180 degrees rotation change may not change screen size. So the clients may draw
- // some frames before and after the display projection transaction is applied by the
- // remote player. That may cause some buffers to show in different rotation. So use
- // sync method to pause clients drawing until the projection transaction is applied.
- mSyncEngine.setSyncMethod(displayTransition.getSyncId(), BLASTSyncEngine.METHOD_BLAST);
- }
final Rect startBounds = displayChange.getStartAbsBounds();
final Rect endBounds = displayChange.getEndAbsBounds();
if (startBounds == null || endBounds == null) return;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4bc4c26..457a555 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -81,7 +81,9 @@
import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.Trace;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Pools;
@@ -174,6 +176,9 @@
*/
protected SparseArray<InsetsSourceProvider> mInsetsSourceProviders = null;
+ @Nullable
+ private ArrayMap<IBinder, DeathRecipient> mInsetsOwnerDeathRecipientMap;
+
// List of children for this window container. List is in z-order as the children appear on
// screen with the top-most window container at the tail of the list.
protected final WindowList<E> mChildren = new WindowList<E>();
@@ -419,11 +424,12 @@
* Adds an {@link InsetsFrameProvider} which describes what insets should be provided to
* this {@link WindowContainer} and its children.
*
- * @param provider describes the insets types and the frames.
+ * @param provider describes the insets type and the frame.
+ * @param owner owns the insets source which only exists when the owner is alive.
*/
- void addLocalInsetsFrameProvider(InsetsFrameProvider provider) {
- if (provider == null) {
- throw new IllegalArgumentException("Insets type not specified.");
+ void addLocalInsetsFrameProvider(InsetsFrameProvider provider, IBinder owner) {
+ if (provider == null || owner == null) {
+ throw new IllegalArgumentException("Insets provider or owner not specified.");
}
if (mDisplayContent == null) {
// This is possible this container is detached when WM shell is responding to a previous
@@ -432,10 +438,26 @@
Slog.w(TAG, "Can't add insets frame provider when detached. " + this);
return;
}
+
+ if (mInsetsOwnerDeathRecipientMap == null) {
+ mInsetsOwnerDeathRecipientMap = new ArrayMap<>();
+ }
+ DeathRecipient deathRecipient = mInsetsOwnerDeathRecipientMap.get(owner);
+ if (deathRecipient == null) {
+ deathRecipient = new DeathRecipient(owner);
+ try {
+ owner.linkToDeath(deathRecipient, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to add source for " + provider + " since the owner has died.");
+ return;
+ }
+ mInsetsOwnerDeathRecipientMap.put(owner, deathRecipient);
+ }
+ final int id = provider.getId();
+ deathRecipient.addSourceId(id);
if (mLocalInsetsSources == null) {
mLocalInsetsSources = new SparseArray<>();
}
- final int id = provider.getId();
if (mLocalInsetsSources.get(id) != null) {
if (DEBUG) {
Slog.d(TAG, "The local insets source for this " + provider
@@ -448,27 +470,77 @@
mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
}
- void removeLocalInsetsFrameProvider(InsetsFrameProvider provider) {
- if (provider == null) {
- throw new IllegalArgumentException("Insets type not specified.");
- }
- if (mLocalInsetsSources == null) {
- return;
+ private class DeathRecipient implements IBinder.DeathRecipient {
+
+ private final IBinder mOwner;
+ private final ArraySet<Integer> mSourceIds = new ArraySet<>();
+
+ DeathRecipient(IBinder owner) {
+ mOwner = owner;
}
- final int id = provider.getId();
- if (mLocalInsetsSources.get(id) == null) {
- if (DEBUG) {
- Slog.d(TAG, "Given " + provider + " doesn't have a local insets source.");
+ void addSourceId(int id) {
+ mSourceIds.add(id);
+ }
+
+ void removeSourceId(int id) {
+ mSourceIds.remove(id);
+ }
+
+ boolean hasSource() {
+ return !mSourceIds.isEmpty();
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mWmService.mGlobalLock) {
+ boolean changed = false;
+ for (int i = mSourceIds.size() - 1; i >= 0; i--) {
+ changed |= removeLocalInsetsSource(mSourceIds.valueAt(i));
+ }
+ mSourceIds.clear();
+ mOwner.unlinkToDeath(this, 0);
+ mInsetsOwnerDeathRecipientMap.remove(mOwner);
+ if (changed && mDisplayContent != null) {
+ mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
+ }
}
- return;
}
- mLocalInsetsSources.remove(id);
+ }
- // Update insets if this window is attached.
- if (mDisplayContent != null) {
+ void removeLocalInsetsFrameProvider(InsetsFrameProvider provider, IBinder owner) {
+ if (provider == null || owner == null) {
+ throw new IllegalArgumentException("Insets provider or owner not specified.");
+ }
+ final int id = provider.getId();
+ if (removeLocalInsetsSource(id) && mDisplayContent != null) {
mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
}
+ if (mInsetsOwnerDeathRecipientMap == null) {
+ return;
+ }
+ final DeathRecipient deathRecipient = mInsetsOwnerDeathRecipientMap.get(owner);
+ if (deathRecipient == null) {
+ return;
+ }
+ deathRecipient.removeSourceId(id);
+ if (!deathRecipient.hasSource()) {
+ owner.unlinkToDeath(deathRecipient, 0);
+ mInsetsOwnerDeathRecipientMap.remove(owner);
+ }
+ }
+
+ private boolean removeLocalInsetsSource(int id) {
+ if (mLocalInsetsSources == null) {
+ return false;
+ }
+ if (mLocalInsetsSources.removeReturnOld(id) == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Given id " + Integer.toHexString(id) + " doesn't exist.");
+ }
+ return false;
+ }
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 9e7df00..805e7ff 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -30,6 +30,7 @@
import android.hardware.display.DisplayManagerInternal;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Message;
import android.util.Pair;
import android.view.ContentRecordingSession;
import android.view.Display;
@@ -515,12 +516,13 @@
* Invalidate all visible windows on a given display, and report back on the callback when all
* windows have redrawn.
*
- * @param callback reporting callback to be called when all windows have redrawn.
+ * @param message The message will be sent when all windows have redrawn. Note that the message
+ * must be obtained from handler, otherwise it will throw NPE.
* @param timeout calls the callback anyway after the timeout.
* @param displayId waits for the windows on the given display, INVALID_DISPLAY to wait for all
* windows on all displays.
*/
- public abstract void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId);
+ public abstract void waitForAllWindowsDrawn(Message message, long timeout, int displayId);
/**
* Overrides the display size.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index bb3d109..0e19671 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -601,7 +601,7 @@
* The callbacks to make when the windows all have been drawn for a given
* {@link WindowContainer}.
*/
- final HashMap<WindowContainer, Runnable> mWaitingForDrawnCallbacks = new HashMap<>();
+ final ArrayMap<WindowContainer<?>, Message> mWaitingForDrawnCallbacks = new ArrayMap<>();
/** List of window currently causing non-system overlay windows to be hidden. */
private ArrayList<WindowState> mHidingNonSystemOverlayWindows = new ArrayList<>();
@@ -763,8 +763,6 @@
Settings.Secure.getUriFor(Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS);
private final Uri mPolicyControlUri =
Settings.Global.getUriFor(Settings.Global.POLICY_CONTROL);
- private final Uri mPointerLocationUri =
- Settings.System.getUriFor(Settings.System.POINTER_LOCATION);
private final Uri mForceDesktopModeOnExternalDisplaysUri = Settings.Global.getUriFor(
Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS);
private final Uri mFreeformWindowUri = Settings.Global.getUriFor(
@@ -792,7 +790,6 @@
resolver.registerContentObserver(mImmersiveModeConfirmationsUri, false, this,
UserHandle.USER_ALL);
resolver.registerContentObserver(mPolicyControlUri, false, this, UserHandle.USER_ALL);
- resolver.registerContentObserver(mPointerLocationUri, false, this, UserHandle.USER_ALL);
resolver.registerContentObserver(mForceDesktopModeOnExternalDisplaysUri, false, this,
UserHandle.USER_ALL);
resolver.registerContentObserver(mFreeformWindowUri, false, this, UserHandle.USER_ALL);
@@ -816,11 +813,6 @@
return;
}
- if (mPointerLocationUri.equals(uri)) {
- updatePointerLocation();
- return;
- }
-
if (mForceDesktopModeOnExternalDisplaysUri.equals(uri)) {
updateForceDesktopModeOnExternalDisplays();
return;
@@ -869,7 +861,6 @@
void loadSettings() {
updateSystemUiSettings(false /* handleChange */);
- updatePointerLocation();
updateMaximumObscuringOpacityForTouch();
}
@@ -900,21 +891,6 @@
}
}
- void updatePointerLocation() {
- ContentResolver resolver = mContext.getContentResolver();
- final boolean enablePointerLocation = Settings.System.getIntForUser(resolver,
- Settings.System.POINTER_LOCATION, 0, UserHandle.USER_CURRENT) != 0;
-
- if (mPointerLocationEnabled == enablePointerLocation) {
- return;
- }
- mPointerLocationEnabled = enablePointerLocation;
- synchronized (mGlobalLock) {
- mRoot.forAllDisplayPolicies(
- p -> p.setPointerLocationEnabled(mPointerLocationEnabled));
- }
- }
-
void updateForceDesktopModeOnExternalDisplays() {
ContentResolver resolver = mContext.getContentResolver();
final boolean enableForceDesktopMode = Settings.Global.getInt(resolver,
@@ -5369,8 +5345,6 @@
public static final int CLIENT_FREEZE_TIMEOUT = 30;
public static final int NOTIFY_ACTIVITY_DRAWN = 32;
- public static final int ALL_WINDOWS_DRAWN = 33;
-
public static final int NEW_ANIMATOR_SCALE = 34;
public static final int SHOW_EMULATOR_DISPLAY_OVERLAY = 36;
@@ -5492,7 +5466,7 @@
}
case WAITING_FOR_DRAWN_TIMEOUT: {
- Runnable callback = null;
+ final Message callback;
final WindowContainer<?> container = (WindowContainer<?>) msg.obj;
synchronized (mGlobalLock) {
ProtoLog.w(WM_ERROR, "Timeout waiting for drawn: undrawn=%s",
@@ -5506,7 +5480,7 @@
callback = mWaitingForDrawnCallbacks.remove(container);
}
if (callback != null) {
- callback.run();
+ callback.sendToTarget();
}
break;
}
@@ -5530,17 +5504,6 @@
}
break;
}
- case ALL_WINDOWS_DRAWN: {
- Runnable callback;
- final WindowContainer container = (WindowContainer) msg.obj;
- synchronized (mGlobalLock) {
- callback = mWaitingForDrawnCallbacks.remove(container);
- }
- if (callback != null) {
- callback.run();
- }
- break;
- }
case NEW_ANIMATOR_SCALE: {
float scale = getCurrentAnimatorScale();
ValueAnimator.setDurationScale(scale);
@@ -6098,7 +6061,8 @@
if (mWaitingForDrawnCallbacks.isEmpty()) {
return;
}
- mWaitingForDrawnCallbacks.forEach((container, callback) -> {
+ for (int i = mWaitingForDrawnCallbacks.size() - 1; i >= 0; i--) {
+ final WindowContainer<?> container = mWaitingForDrawnCallbacks.keyAt(i);
for (int j = container.mWaitingForDrawn.size() - 1; j >= 0; j--) {
final WindowState win = (WindowState) container.mWaitingForDrawn.get(j);
ProtoLog.i(WM_DEBUG_SCREEN_ON,
@@ -6124,9 +6088,9 @@
if (container.mWaitingForDrawn.isEmpty()) {
ProtoLog.d(WM_DEBUG_SCREEN_ON, "All windows drawn!");
mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, container);
- mH.sendMessage(mH.obtainMessage(H.ALL_WINDOWS_DRAWN, container));
+ mWaitingForDrawnCallbacks.removeAt(i).sendToTarget();
}
- });
+ }
}
private void traceStartWaitingForWindowDrawn(WindowState window) {
@@ -7842,13 +7806,14 @@
}
@Override
- public void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId) {
+ public void waitForAllWindowsDrawn(Message message, long timeout, int displayId) {
+ Objects.requireNonNull(message.getTarget());
final WindowContainer<?> container = displayId == INVALID_DISPLAY
? mRoot : mRoot.getDisplayContent(displayId);
if (container == null) {
// The waiting container doesn't exist, no need to wait to run the callback. Run and
// return;
- callback.run();
+ message.sendToTarget();
return;
}
boolean allWindowsDrawn = false;
@@ -7865,13 +7830,13 @@
}
}
- mWaitingForDrawnCallbacks.put(container, callback);
+ mWaitingForDrawnCallbacks.put(container, message);
mH.sendNewMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, container, timeout);
checkDrawnWindowsLocked();
}
}
if (allWindowsDrawn) {
- callback.run();
+ message.sendToTarget();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a2f7ba4..d1b00a3 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -321,9 +321,18 @@
applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller);
return transition.getToken();
}
- transition.start();
transition.mLogger.mStartWCT = wct;
- applyTransaction(wct, -1 /*syncId*/, transition, caller);
+ if (transition.shouldApplyOnDisplayThread()) {
+ mService.mH.post(() -> {
+ synchronized (mService.mGlobalLock) {
+ transition.start();
+ applyTransaction(wct, -1 /* syncId */, transition, caller);
+ }
+ });
+ } else {
+ transition.start();
+ applyTransaction(wct, -1 /* syncId */, transition, caller);
+ }
// Since the transition is already provided, it means WMCore is determining the
// "readiness lifecycle" outside the provided transaction, so don't set ready here.
return transition.getToken();
@@ -1081,7 +1090,8 @@
+ container);
break;
}
- container.addLocalInsetsFrameProvider(hop.getInsetsFrameProvider());
+ container.addLocalInsetsFrameProvider(
+ hop.getInsetsFrameProvider(), hop.getInsetsFrameOwner());
break;
}
case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER: {
@@ -1091,7 +1101,8 @@
+ container);
break;
}
- container.removeLocalInsetsFrameProvider(hop.getInsetsFrameProvider());
+ container.removeLocalInsetsFrameProvider(
+ hop.getInsetsFrameProvider(), hop.getInsetsFrameOwner());
break;
}
case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index cf1e51f..0d4c2d6 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5665,7 +5665,7 @@
}
if (mIsWallpaper) {
// TODO(b/233286785): Add sync support to wallpaper.
- return false;
+ return true;
}
// In the WindowContainer implementation we immediately mark ready
// since a generic WindowContainer only needs to wait for its
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index e76cbe44..c065cb5 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -971,7 +971,7 @@
void NativeInputManager::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
const std::set<gui::Uid>& uids) {
static const bool ENABLE_INPUT_DEVICE_USAGE_METRICS =
- sysprop::InputProperties::enable_input_device_usage_metrics().value_or(false);
+ sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true);
if (!ENABLE_INPUT_DEVICE_USAGE_METRICS) return;
mInputManager->getMetricsCollector().notifyDeviceInteraction(deviceId, timestamp, uids);
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index ce022e9..57b5d00 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -53,6 +53,7 @@
<xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="brightnessThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="refreshRateThermalThrottlingMapId" type="xs:string" minOccurs="0" />
+ <xs:element name="leadDisplayAddress" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="enabled" type="xs:boolean" use="optional" />
<xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" />
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index 42a800d..2d4f7a4 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -6,6 +6,7 @@
method public java.math.BigInteger getAddress();
method public String getBrightnessThrottlingMapId();
method public String getDisplayGroup();
+ method public java.math.BigInteger getLeadDisplayAddress();
method public String getPosition();
method public String getRefreshRateThermalThrottlingMapId();
method public String getRefreshRateZoneId();
@@ -16,6 +17,7 @@
method public void setDefaultDisplay(boolean);
method public void setDisplayGroup(String);
method public void setEnabled(boolean);
+ method public void setLeadDisplayAddress(java.math.BigInteger);
method public void setPosition(String);
method public void setRefreshRateThermalThrottlingMapId(String);
method public void setRefreshRateZoneId(String);
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
index e33ca77..b7a0cf3 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
@@ -138,6 +138,14 @@
}
@Test
+ public void testGetUserMinAspectRatio_withCrossUserId() {
+ final int crossUserId = UserHandle.myUserId() + 1;
+ assertThrows(SecurityException.class,
+ () -> mIPackageManager.getUserMinAspectRatio(
+ mInstrumentation.getContext().getPackageName(), crossUserId));
+ }
+
+ @Test
public void testIsPackageSignedByKeySet_cannotDetectCrossUserPkg() throws Exception {
final KeySet keySet = mIPackageManager.getSigningKeySet(mContext.getPackageName());
assertThrows(IllegalArgumentException.class,
diff --git a/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6
index 30bb647..6feebb8 100644
--- a/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6
+++ b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6
Binary files differ
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
index 435f0d7..a805e5c 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -20,6 +20,7 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import static java.lang.reflect.Modifier.isFinal;
@@ -138,7 +139,7 @@
.setSecondaryCpuAbiString("secondaryCpuAbiString")
.setCpuAbiOverrideString("cpuAbiOverrideString")
.build();
- pri.populateUsers(new int[] {
+ pri.populateUsers(new int[]{
1, 2, 3, 4, 5
}, setting);
Assert.assertNotNull(pri.mBroadcastUsers);
@@ -150,7 +151,7 @@
pri.mBroadcastUsers = null;
final int EXCLUDED_USER_ID = 4;
setting.setInstantApp(true, EXCLUDED_USER_ID);
- pri.populateUsers(new int[] {
+ pri.populateUsers(new int[]{
1, 2, 3, EXCLUDED_USER_ID, 5
}, setting);
Assert.assertNotNull(pri.mBroadcastUsers);
@@ -164,8 +165,8 @@
@Test
public void testPartitions() {
- String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" };
- String[] appdir = { "app", "priv-app" };
+ String[] partitions = {"system", "vendor", "odm", "oem", "product", "system_ext"};
+ String[] appdir = {"app", "priv-app"};
for (int i = 0; i < partitions.length; i++) {
final ScanPartition scanPartition =
PackageManagerService.SYSTEM_PARTITIONS.get(i);
@@ -425,10 +426,10 @@
private String displayName(Method m) {
String r = m.getName();
String p = Arrays.toString(m.getGenericParameterTypes())
- .replaceAll("([a-zA-Z0-9]+\\.)+", "")
- .replace("class ", "")
- .replaceAll("^\\[", "(")
- .replaceAll("\\]$", ")");
+ .replaceAll("([a-zA-Z0-9]+\\.)+", "")
+ .replace("class ", "")
+ .replaceAll("^\\[", "(")
+ .replaceAll("\\]$", ")");
return r + p;
}
@@ -612,4 +613,22 @@
runShellCommand("pm uninstall " + TEST_PKG_NAME);
}
}
+
+ @Test
+ public void testSetUserMinAspectRatio_samePackage_succeeds() throws Exception {
+ mIPackageManager.setUserMinAspectRatio(PACKAGE_NAME, UserHandle.myUserId(),
+ PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+ // Invoking setUserMinAspectRatio on the same package shouldn't get any exception.
+ }
+
+ @Test
+ public void testSetUserMinAspectRatio_differentPackage_fails() {
+ final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
+ runShellCommand("pm install " + testApk);
+ assertThrows(SecurityException.class, () -> {
+ mIPackageManager.setUserMinAspectRatio(TEST_PKG_NAME, UserHandle.myUserId(),
+ PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+ });
+ runShellCommand("pm uninstall " + TEST_PKG_NAME);
+ }
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 836f858..16fb012 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -971,7 +971,7 @@
origPkgSetting01.setUserState(0, 100, 1, true, false, false, false, 0, null, false,
false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}),
new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning",
- "splashScreenTheme", 1000L);
+ "splashScreenTheme", 1000L, PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
final PersistableBundle appExtras1 = createPersistableBundle(
PACKAGE_NAME_1, 1L, 0.01, true, "appString1");
final PersistableBundle launcherExtras1 = createPersistableBundle(
@@ -1638,7 +1638,8 @@
: oldUserState.getSharedLibraryOverlayPaths() == null)
&& userState.getSplashScreenTheme().equals(
oldUserState.getSplashScreenTheme())
- && userState.getUninstallReason() == oldUserState.getUninstallReason();
+ && userState.getUninstallReason() == oldUserState.getUninstallReason()
+ && userState.getMinAspectRatio() == oldUserState.getMinAspectRatio();
}
private SharedUserSetting createSharedUserSetting(Settings settings, String userName,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index 130e6ad..4b124ca 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -19,8 +19,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
-import android.view.Display;
import android.view.DisplayAddress;
import androidx.test.filters.SmallTest;
@@ -65,9 +65,15 @@
Layout testLayout = new Layout();
createDefaultDisplay(testLayout, 123456L);
- createNonDefaultDisplay(testLayout, 78910L, /* enabled= */ false, /* group= */ null);
- createNonDefaultDisplay(testLayout, 98765L, /* enabled= */ true, /* group= */ "group1");
- createNonDefaultDisplay(testLayout, 786L, /* enabled= */ false, /* group= */ "group2");
+ createNonDefaultDisplay(testLayout, 78910L, /* enabled= */ false, /* group= */ null,
+ /* leadDisplayAddress= */ null);
+ createNonDefaultDisplay(testLayout, 98765L, /* enabled= */ true, /* group= */ "group1",
+ /* leadDisplayAddress= */ null);
+ createNonDefaultDisplay(testLayout, 786L, /* enabled= */ false, /* group= */ "group2",
+ /* leadDisplayAddress= */ null);
+ createNonDefaultDisplay(testLayout, 1092L, /* enabled= */ true, /* group= */ null,
+ DisplayAddress.fromPhysicalDisplayId(78910L));
+ testLayout.postProcessLocked();
assertEquals(testLayout, configLayout);
}
@@ -78,7 +84,9 @@
Layout testLayout = new Layout();
createDefaultDisplay(testLayout, 78910L);
- createNonDefaultDisplay(testLayout, 123456L, /* enabled= */ false, /* group= */ null);
+ createNonDefaultDisplay(testLayout, 123456L, /* enabled= */ false, /* group= */ null,
+ /* leadDisplayAddress= */ null);
+ testLayout.postProcessLocked();
assertEquals(testLayout, configLayout);
}
@@ -122,20 +130,132 @@
Layout testLayout = new Layout();
testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(345L),
/* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
- mDisplayIdProducerMock, Layout.Display.POSITION_FRONT, Display.DEFAULT_DISPLAY,
- /* brightnessThrottlingMapId= */ "brightness1",
+ mDisplayIdProducerMock, Layout.Display.POSITION_FRONT,
+ /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness1",
/* refreshRateZoneId= */ "zone1",
/* refreshRateThermalThrottlingMapId= */ "rr1");
testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),
/* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",
- mDisplayIdProducerMock, Layout.Display.POSITION_REAR, Display.DEFAULT_DISPLAY,
- /* brightnessThrottlingMapId= */ "brightness2",
+ mDisplayIdProducerMock, Layout.Display.POSITION_REAR,
+ /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness2",
/* refreshRateZoneId= */ "zone2",
/* refreshRateThermalThrottlingMapId= */ "rr2");
+ testLayout.postProcessLocked();
assertEquals(testLayout, configLayout);
}
+ @Test
+ public void testLeadDisplayAddress() {
+ Layout layout = new Layout();
+ createNonDefaultDisplay(layout, 111L, /* enabled= */ true, /* group= */ null,
+ /* leadDisplayAddress= */ null);
+ createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+ DisplayAddress.fromPhysicalDisplayId(111L));
+
+ layout.postProcessLocked();
+
+ com.android.server.display.layout.Layout.Display display111 =
+ layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(111));
+ com.android.server.display.layout.Layout.Display display222 =
+ layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(222));
+ assertEquals(display111.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+ assertEquals(display222.getLeadDisplayId(), display111.getLogicalDisplayId());
+ }
+
+ @Test
+ public void testLeadDisplayAddress_defaultDisplay() {
+ Layout layout = new Layout();
+ createDefaultDisplay(layout, 123456L);
+
+ layout.postProcessLocked();
+
+ com.android.server.display.layout.Layout.Display display =
+ layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(123456));
+ assertEquals(display.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+ }
+
+ @Test
+ public void testLeadDisplayAddress_noLeadDisplay() {
+ Layout layout = new Layout();
+ createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+ /* leadDisplayAddress= */ null);
+
+ layout.postProcessLocked();
+
+ com.android.server.display.layout.Layout.Display display =
+ layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(222));
+ assertEquals(display.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+ }
+
+ @Test
+ public void testLeadDisplayAddress_selfLeadDisplayForNonDefaultDisplay() {
+ Layout layout = new Layout();
+
+ assertThrows("Expected Layout to throw IllegalArgumentException when the display points out"
+ + " itself as a lead display",
+ IllegalArgumentException.class,
+ () -> layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(123L),
+ /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+ mDisplayIdProducerMock, Layout.Display.POSITION_FRONT,
+ DisplayAddress.fromPhysicalDisplayId(123L),
+ /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+ /* refreshRateThermalThrottlingMapId= */ null));
+ }
+
+ @Test
+ public void testLeadDisplayAddress_wrongLeadDisplayForDefaultDisplay() {
+ Layout layout = new Layout();
+
+ assertThrows("Expected Layout to throw IllegalArgumentException when the default display "
+ + "has a lead display",
+ IllegalArgumentException.class,
+ () -> layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(123L),
+ /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+ mDisplayIdProducerMock, Layout.Display.POSITION_FRONT,
+ DisplayAddress.fromPhysicalDisplayId(987L),
+ /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+ /* refreshRateThermalThrottlingMapId= */ null));
+ }
+
+ @Test
+ public void testLeadDisplayAddress_notExistingLeadDisplayForNonDefaultDisplay() {
+ Layout layout = new Layout();
+ createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+ DisplayAddress.fromPhysicalDisplayId(111L));
+
+ assertThrows("Expected Layout to throw IllegalArgumentException when a lead display doesn't"
+ + " exist", IllegalArgumentException.class, () -> layout.postProcessLocked());
+ }
+
+ @Test
+ public void testLeadDisplayAddress_leadDisplayInDifferentDisplayGroup() {
+ Layout layout = new Layout();
+ createNonDefaultDisplay(layout, 111, /* enabled= */ true, /* group= */ "group1",
+ /* leadDisplayAddress= */ null);
+ createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ "group2",
+ DisplayAddress.fromPhysicalDisplayId(111L));
+
+ assertThrows("Expected Layout to throw IllegalArgumentException when pointing to a lead "
+ + "display in the different group",
+ IllegalArgumentException.class, () -> layout.postProcessLocked());
+ }
+
+ @Test
+ public void testLeadDisplayAddress_cyclicLeadDisplay() {
+ Layout layout = new Layout();
+ createNonDefaultDisplay(layout, 111, /* enabled= */ true, /* group= */ null,
+ DisplayAddress.fromPhysicalDisplayId(222L));
+ createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+ DisplayAddress.fromPhysicalDisplayId(333L));
+ createNonDefaultDisplay(layout, 333L, /* enabled= */ true, /* group= */ null,
+ DisplayAddress.fromPhysicalDisplayId(222L));
+
+ assertThrows("Expected Layout to throw IllegalArgumentException when pointing to a lead "
+ + "display in the different group",
+ IllegalArgumentException.class, () -> layout.postProcessLocked());
+ }
+
////////////////////
// Helper Methods //
////////////////////
@@ -145,10 +265,11 @@
mDisplayIdProducerMock);
}
- private void createNonDefaultDisplay(Layout layout, long id, boolean enabled, String group) {
+ private void createNonDefaultDisplay(Layout layout, long id, boolean enabled, String group,
+ DisplayAddress leadDisplayAddress) {
layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id), /* isDefault= */ false,
enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
- Display.DEFAULT_DISPLAY, /* brightnessThrottlingMapId= */ null,
+ leadDisplayAddress, /* brightnessThrottlingMapId= */ null,
/* refreshRateZoneId= */ null,
/* refreshRateThermalThrottlingMapId= */ null);
}
@@ -177,6 +298,10 @@
+ "<display enabled=\"false\" displayGroup=\"group2\">\n"
+ "<address>786</address>\n"
+ "</display>\n"
+ + "<display enabled=\"true\">\n"
+ + "<address>1092</address>\n"
+ + "<leadDisplayAddress>78910</leadDisplayAddress>\n"
+ + "</display>\n"
+ "</layout>\n"
+ "<layout>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 1eec70d..3e695c9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -604,13 +604,13 @@
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
/* isDefault= */ true, /* isEnabled= */ true, /* displayGroup= */ null,
mIdProducer, POSITION_UNKNOWN,
- /* leadDisplayId= */ Display.DEFAULT_DISPLAY,
+ /* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ "concurrent",
/* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
/* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null,
mIdProducer, POSITION_UNKNOWN,
- /* leadDisplayId= */ Display.DEFAULT_DISPLAY,
+ /* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ "concurrent",
/* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
@@ -646,7 +646,7 @@
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device1)
.getLeadDisplayIdLocked());
- assertEquals(0, mLogicalDisplayMapper.getDisplayLocked(device2)
+ assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device2)
.getLeadDisplayIdLocked());
assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1)
.getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
@@ -811,7 +811,7 @@
mIdProducer);
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
/* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null,
- mIdProducer, POSITION_REAR, Display.DEFAULT_DISPLAY,
+ mIdProducer, POSITION_REAR, /* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
/* refreshRateThermalThrottlingMapId= */null);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
@@ -861,7 +861,7 @@
private void createNonDefaultDisplay(Layout layout, DisplayAddress address, boolean enabled,
String group) {
layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,
- Layout.Display.POSITION_UNKNOWN, Display.DEFAULT_DISPLAY,
+ Layout.Display.POSITION_UNKNOWN, /* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
/* refreshRateThermalThrottlingMapId= */ null);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index f89f73c9..aa0a2fe 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -1257,6 +1257,17 @@
public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
return mSurfaceControlProxy;
}
+
+ // Instead of using DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay)
+ // we should use DisplayDeviceConfig.create(context, isFirstDisplay) for the test to ensure
+ // that real device DisplayDeviceConfig is not loaded for FakeDisplay and we are getting
+ // consistent behaviour. Please also note that context passed to this method, is
+ // mMockContext and values will be loaded from mMockResources.
+ @Override
+ public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
+ long physicalDisplayId, boolean isFirstDisplay) {
+ return DisplayDeviceConfig.create(context, isFirstDisplay);
+ }
}
private class TestListener implements DisplayAdapter.Listener {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index e7b3e6f..617e4eb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -325,6 +325,83 @@
() -> mUmi.getBootUser(/* waitUntilSet= */ false));
}
+ @Test
+ public void testGetPreviousFullUserToEnterForeground() throws Exception {
+ addUser(USER_ID);
+ setLastForegroundTime(USER_ID, 1_000_000L);
+ addUser(OTHER_USER_ID);
+ setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+ assertWithMessage("getPreviousFullUserToEnterForeground")
+ .that(mUms.getPreviousFullUserToEnterForeground())
+ .isEqualTo(OTHER_USER_ID);
+ }
+
+ @Test
+ public void testGetPreviousFullUserToEnterForeground_SkipsCurrentUser() throws Exception {
+ addUser(USER_ID);
+ setLastForegroundTime(USER_ID, 1_000_000L);
+ addUser(OTHER_USER_ID);
+ setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+ mockCurrentUser(OTHER_USER_ID);
+ assertWithMessage("getPreviousFullUserToEnterForeground should skip current user")
+ .that(mUms.getPreviousFullUserToEnterForeground())
+ .isEqualTo(USER_ID);
+ }
+
+ @Test
+ public void testGetPreviousFullUserToEnterForeground_SkipsNonFullUsers() throws Exception {
+ addUser(USER_ID);
+ setLastForegroundTime(USER_ID, 1_000_000L);
+ addUser(OTHER_USER_ID);
+ setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+ mUsers.get(OTHER_USER_ID).info.flags &= ~UserInfo.FLAG_FULL;
+ assertWithMessage("getPreviousFullUserToEnterForeground should skip non-full users")
+ .that(mUms.getPreviousFullUserToEnterForeground())
+ .isEqualTo(USER_ID);
+ }
+
+ @Test
+ public void testGetPreviousFullUserToEnterForeground_SkipsPartialUsers() throws Exception {
+ addUser(USER_ID);
+ setLastForegroundTime(USER_ID, 1_000_000L);
+ addUser(OTHER_USER_ID);
+ setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+ mUsers.get(OTHER_USER_ID).info.partial = true;
+ assertWithMessage("getPreviousFullUserToEnterForeground should skip partial users")
+ .that(mUms.getPreviousFullUserToEnterForeground())
+ .isEqualTo(USER_ID);
+ }
+
+ @Test
+ public void testGetPreviousFullUserToEnterForeground_SkipsDisabledUsers() throws Exception {
+ addUser(USER_ID);
+ setLastForegroundTime(USER_ID, 1_000_000L);
+ addUser(OTHER_USER_ID);
+ setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+ mUsers.get(OTHER_USER_ID).info.flags |= UserInfo.FLAG_DISABLED;
+ assertWithMessage("getPreviousFullUserToEnterForeground should skip disabled users")
+ .that(mUms.getPreviousFullUserToEnterForeground())
+ .isEqualTo(USER_ID);
+ }
+
+ @Test
+ public void testGetPreviousFullUserToEnterForeground_SkipsRemovingUsers() throws Exception {
+ addUser(USER_ID);
+ setLastForegroundTime(USER_ID, 1_000_000L);
+ addUser(OTHER_USER_ID);
+ setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+ mUms.addRemovingUserId(OTHER_USER_ID);
+ assertWithMessage("getPreviousFullUserToEnterForeground should skip removing users")
+ .that(mUms.getPreviousFullUserToEnterForeground())
+ .isEqualTo(USER_ID);
+ }
+
private void mockCurrentUser(@UserIdInt int userId) {
mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
index a3a49d70..f3aa427 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
@@ -259,6 +259,29 @@
}
@Test
+ public void testMinTimeBetweenAlarms_freshAlarm() {
+ final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 5 * MINUTE_IN_MILLIS);
+ final long fixedTimeElapsed = mInjector.getElapsedRealtime();
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ final String pkg1 = "com.android.test.1";
+ final String pkg2 = "com.android.test.2";
+ alarmQueue.addAlarm(pkg1, fixedTimeElapsed + MINUTE_IN_MILLIS);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+ anyInt(), eq(fixedTimeElapsed + MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any());
+
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+
+ alarmQueue.onAlarm();
+ // Minimum of 5 minutes between alarms, so the next alarm should be 5 minutes after the
+ // first.
+ alarmQueue.addAlarm(pkg2, fixedTimeElapsed + 2 * MINUTE_IN_MILLIS);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+ anyInt(), eq(fixedTimeElapsed + 6 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any());
+ }
+
+ @Test
public void testOnAlarm() {
final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0);
final long nowElapsed = mInjector.getElapsedRealtime();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index e6ef044..5b5c8d4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -29,6 +29,7 @@
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -202,6 +203,7 @@
assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_0));
assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_1));
+ // Once for each display on unregister
verify(mMockThumbnail, times(2)).hideThumbnail();
}
@@ -543,7 +545,11 @@
// The first time is triggered when the thumbnail is just created.
// The second time is triggered when the magnification region changed.
verify(mMockThumbnail, times(2)).setThumbnailBounds(
- any(), anyFloat(), anyFloat(), anyFloat());
+ /* currentBounds= */ any(),
+ /* scale= */ anyFloat(),
+ /* centerX= */ anyFloat(),
+ /* centerY= */ anyFloat()
+ );
}
@Test
@@ -681,6 +687,9 @@
checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId);
assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2));
checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId);
+
+ // Once on init before it's activated and once for reset
+ verify(mMockThumbnail, times(2)).hideThumbnail();
}
@Test
@@ -783,6 +792,9 @@
mMessageCapturingHandler.sendAllMessages();
checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_0);
checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_1);
+
+ // Twice for each display: once on init before it's activated and once for screen off
+ verify(mMockThumbnail, times(4)).hideThumbnail();
}
@Test
@@ -847,6 +859,15 @@
mMessageCapturingHandler.sendAllMessages();
checkActivatedAndMagnifying(
/* activated= */ expectedActivated, /* magnifying= */ false, displayId);
+
+ if (expectedActivated) {
+ verify(mMockThumbnail, times(2)).setThumbnailBounds(
+ /* currentBounds= */ any(),
+ /* scale= */ anyFloat(),
+ /* centerX= */ anyFloat(),
+ /* centerY= */ anyFloat()
+ );
+ }
}
@Test
@@ -950,6 +971,13 @@
INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
+
+ verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds(
+ /* currentBounds= */ any(),
+ eq(scale),
+ /* centerX= */ anyFloat(),
+ /* centerY= */ anyFloat()
+ );
}
@Test
@@ -984,6 +1012,13 @@
INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
+
+ verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds(
+ /* currentBounds= */ any(),
+ eq(scale),
+ /* centerX= */ anyFloat(),
+ /* centerY= */ anyFloat()
+ );
}
@Test
@@ -1246,6 +1281,13 @@
callbacks.onImeWindowVisibilityChanged(true);
mMessageCapturingHandler.sendAllMessages();
verify(mRequestObserver).onImeWindowVisibilityChanged(eq(DISPLAY_0), eq(true));
+
+ verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds(
+ /* currentBounds= */ any(),
+ /* scale= */ anyFloat(),
+ /* centerX= */ anyFloat(),
+ /* centerY= */ anyFloat()
+ );
}
@Test
@@ -1270,6 +1312,15 @@
mFullScreenMagnificationController.onUserContextChanged(DISPLAY_0);
verify(mRequestObserver).onFullScreenMagnificationActivationState(eq(DISPLAY_0), eq(false));
+ verify(mMockThumbnail).setThumbnailBounds(
+ /* currentBounds= */ any(),
+ /* scale= */ anyFloat(),
+ /* centerX= */ anyFloat(),
+ /* centerY= */ anyFloat()
+ );
+
+ // Once on init before it's activated and once for reset
+ verify(mMockThumbnail, times(2)).hideThumbnail();
}
@Test
@@ -1281,6 +1332,12 @@
assertEquals(1.0f, mFullScreenMagnificationController.getScale(DISPLAY_0), 0);
assertTrue(mFullScreenMagnificationController.isActivated(DISPLAY_0));
+ verify(mMockThumbnail).setThumbnailBounds(
+ /* currentBounds= */ any(),
+ /* scale= */ anyFloat(),
+ /* centerX= */ anyFloat(),
+ /* centerY= */ anyFloat()
+ );
}
private void setScaleToMagnifying() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index e75fc21..81dd961 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -666,6 +666,21 @@
assertEquals(config.getCenterX(), actualConfig.getCenterX(), 0);
assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0);
assertEquals(config.getScale(), actualConfig.getScale(), 0);
+
+ verify(mWindowMagnificationManager).onUserMagnificationScaleChanged(
+ /* userId= */ anyInt(), eq(TEST_DISPLAY), eq(config.getScale()));
+ }
+
+ @Test
+ public void onSourceBoundChanged_windowEnabled_notifyMagnificationChanged()
+ throws RemoteException {
+ setMagnificationEnabled(MODE_WINDOW);
+ reset(mWindowMagnificationManager);
+
+ mMagnificationController.onSourceBoundsChanged(TEST_DISPLAY, TEST_RECT);
+
+ verify(mWindowMagnificationManager).onUserMagnificationScaleChanged(
+ /* userId= */ anyInt(), eq(TEST_DISPLAY), eq(DEFAULT_SCALE));
}
@Test
@@ -794,6 +809,7 @@
verify(mMagnificationController).onWindowMagnificationActivationState(
eq(TEST_DISPLAY), eq(true));
}
+
@Test
public void deactivateWindowMagnification_windowActivated_triggerCallbackAndLogUsage()
throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
index 3baa102..8faddf8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
@@ -187,6 +187,29 @@
.addView(eq(mMagnificationThumbnail.mThumbnailLayout), any());
verify(mMockWindowManager, never())
.removeView(eq(mMagnificationThumbnail.mThumbnailLayout));
+ verify(mMockWindowManager, never())
+ .updateViewLayout(eq(mMagnificationThumbnail.mThumbnailLayout), any());
+ }
+
+ @Test
+ public void whenVisible_setBoundsUpdatesLayout() throws InterruptedException {
+ runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
+ /* scale= */ 2f,
+ /* centerX= */ 5,
+ /* centerY= */ 10
+ ));
+ runOnMainSync(() -> mMagnificationThumbnail.setThumbnailBounds(
+ new Rect(),
+ /* scale= */ 2f,
+ /* centerX= */ 5,
+ /* centerY= */ 10
+ ));
+ idle();
+
+ verify(mMockWindowManager).updateViewLayout(
+ eq(mMagnificationThumbnail.mThumbnailLayout),
+ /* params= */ any()
+ );
}
private static void idle() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
index 2357e65..8608199 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
@@ -122,6 +122,15 @@
}
@Test
+ public void onUserMagnificationScaleChanged() throws RemoteException {
+ final int testUserId = 1;
+ final float testScale = 3f;
+ mConnectionWrapper.onUserMagnificationScaleChanged(testUserId, TEST_DISPLAY, testScale);
+ verify(mConnection).onUserMagnificationScaleChanged(
+ eq(testUserId), eq(TEST_DISPLAY), eq(testScale));
+ }
+
+ @Test
public void setMirrorWindowCallback() throws RemoteException {
mConnectionWrapper.setConnectionCallback(mCallback);
verify(mConnection).setConnectionCallback(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index c98de7c..e8b337a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -548,6 +548,18 @@
}
@Test
+ public void onUserMagnificationScaleChanged_hasConnection_invokeConnectionMethod()
+ throws RemoteException {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+
+ final float testScale = 3f;
+ mWindowMagnificationManager.onUserMagnificationScaleChanged(
+ CURRENT_USER_ID, TEST_DISPLAY, testScale);
+ verify(mMockConnection.getConnection()).onUserMagnificationScaleChanged(
+ eq(CURRENT_USER_ID), eq(TEST_DISPLAY), eq(testScale));
+ }
+
+ @Test
public void pointersInWindow_magnifierEnabled_returnCorrectValue() throws RemoteException {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index dccacb4..24a628e 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -1342,6 +1342,10 @@
Message copy = new Message();
copy.copyFrom(msg);
mMessages.add(copy);
+ if (msg.getCallback() != null) {
+ msg.getCallback().run();
+ msg.setCallback(null);
+ }
return super.sendMessageAtTime(msg, uptimeMillis);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index c42928e..bb8b986 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -38,6 +38,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
import android.app.ActivityManagerInternal;
@@ -49,10 +50,14 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionCallback;
+import android.media.projection.IMediaProjectionWatcherCallback;
import android.media.projection.ReviewGrantedConsentResult;
+import android.os.Binder;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.view.ContentRecordingSession;
@@ -86,6 +91,7 @@
private static final int UID = 10;
private static final String PACKAGE_NAME = "test.package";
private final ApplicationInfo mAppInfo = new ApplicationInfo();
+ private final TestLooper mTestLooper = new TestLooper();
private static final ContentRecordingSession DISPLAY_SESSION =
ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
// Callback registered by an app on a MediaProjection instance.
@@ -110,6 +116,14 @@
}
};
+ private final MediaProjectionManagerService.Injector mTestLooperInjector =
+ new MediaProjectionManagerService.Injector() {
+ @Override
+ Looper createCallbackLooper() {
+ return mTestLooper.getLooper();
+ }
+ };
+
private Context mContext;
private MediaProjectionManagerService mService;
private OffsettableClock mClock;
@@ -122,12 +136,15 @@
private WindowManagerInternal mWindowManagerInternal;
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private IMediaProjectionWatcherCallback mWatcherCallback;
@Captor
private ArgumentCaptor<ContentRecordingSession> mSessionCaptor;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
+ when(mWatcherCallback.asBinder()).thenReturn(new Binder());
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
LocalServices.addService(ActivityManagerInternal.class, mAmInternal);
@@ -671,6 +688,59 @@
assertThat(mService.isCurrentProjection(projection)).isTrue();
}
+ @Test
+ public void setContentRecordingSession_successful_notifiesListeners()
+ throws Exception {
+ mService.addCallback(mWatcherCallback);
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+ projection.start(mIMediaProjectionCallback);
+
+ doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+ mService.setContentRecordingSession(DISPLAY_SESSION);
+
+ verify(mWatcherCallback).onRecordingSessionSet(
+ projection.getProjectionInfo(),
+ DISPLAY_SESSION
+ );
+ }
+
+ @Test
+ public void setContentRecordingSession_notifiesListenersOnCallbackLooper()
+ throws Exception {
+ mService = new MediaProjectionManagerService(mContext, mTestLooperInjector);
+ mService.addCallback(mWatcherCallback);
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+ projection.start(mIMediaProjectionCallback);
+ doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+
+ mService.setContentRecordingSession(DISPLAY_SESSION);
+ // Callback not notified yet, as test looper hasn't dispatched the message yet
+ verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any());
+
+ mTestLooper.dispatchAll();
+ // Message dispatched on test looper. Callback should now be notified.
+ verify(mWatcherCallback).onRecordingSessionSet(
+ projection.getProjectionInfo(),
+ DISPLAY_SESSION
+ );
+ }
+
+ @Test
+ public void setContentRecordingSession_failure_doesNotNotifyListeners()
+ throws Exception {
+ mService.addCallback(mWatcherCallback);
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+ projection.start(mIMediaProjectionCallback);
+
+ doReturn(false).when(mWindowManagerInternal).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+ mService.setContentRecordingSession(DISPLAY_SESSION);
+
+ verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any());
+ }
+
private void verifySetSessionWithContent(@ContentRecordingSession.RecordContent int content) {
verify(mWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
mSessionCaptor.capture());
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
index fdf94be..39cc653 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
@@ -182,7 +182,7 @@
UserInfo secondaryUser = addUser();
UserInfo profile = addProfile(secondaryUser);
// Add the profile it to the users being removed.
- mUserManagerService.addRemovingUserIdLocked(profile.id);
+ mUserManagerService.addRemovingUserId(profile.id);
// We should reuse the badge from the profile being removed.
assertEquals("Badge index not reused while removing a user", 0,
mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id,
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java
index 1f4c9f8..b6fd65e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java
@@ -111,7 +111,7 @@
private void removeUser(int userId) {
mUserManagerService.removeUserInfo(userId);
- mUserManagerService.addRemovingUserIdLocked(userId);
+ mUserManagerService.addRemovingUserId(userId);
}
private void assertNoNextIdAvailable(String message) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 4af0323..592be2d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -70,6 +70,9 @@
LocalServices.removeServiceForTest(UserManagerInternal.class);
mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext());
+ // Put the current user to mUsers. UMS can't find userlist.xml, and fallbackToSingleUserLP.
+ mUserManagerService.putUserInfo(
+ new UserInfo(ActivityManager.getCurrentUser(), "Current User", 0));
restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml");
restrictionsFile.delete();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ba07439..30180e8 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -62,6 +62,8 @@
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
+import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
@@ -83,6 +85,9 @@
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
+import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
+import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
@@ -186,6 +191,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
+import android.os.Parcelable;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.Process;
@@ -716,6 +722,7 @@
@After
public void assertAllWakeLocksReleased() {
+ waitForIdle(); // Finish async work.
for (WakeLock wakeLock : mAcquiredWakeLocks) {
assertThat(wakeLock.isHeld()).isFalse();
}
@@ -867,13 +874,18 @@
}
private NotificationRecord generateNotificationRecord(NotificationChannel channel, int userId) {
+ return generateNotificationRecord(channel, 1, userId);
+ }
+
+ private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
+ int userId) {
if (channel == null) {
channel = mTestNotificationChannel;
}
Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon);
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, "tag", mUid, 0,
nb.build(), new UserHandle(userId), null, 0);
return new NotificationRecord(mContext, sbn, channel);
}
@@ -5905,6 +5917,26 @@
}
@Test
+ public void testVisitUris_wearableExtender() {
+ Icon actionIcon = Icon.createWithContentUri("content://media/action");
+ Icon wearActionIcon = Icon.createWithContentUri("content://media/wearAction");
+ PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent(),
+ PendingIntent.FLAG_IMMUTABLE);
+ Notification n = new Notification.Builder(mContext, "a")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .addAction(new Notification.Action.Builder(actionIcon, "Hey!", intent).build())
+ .extend(new Notification.WearableExtender().addAction(
+ new Notification.Action.Builder(wearActionIcon, "Wear!", intent).build()))
+ .build();
+
+ Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+ n.visitUris(visitor);
+
+ verify(visitor).accept(eq(actionIcon.getUri()));
+ verify(visitor).accept(eq(wearActionIcon.getUri()));
+ }
+
+ @Test
public void testSetNotificationPolicy_preP_setOldFields() {
ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = mZenModeHelper;
@@ -10734,6 +10766,51 @@
}
@Test
+ public void testUngroupingAutoSummary_differentUsers() throws Exception {
+ NotificationRecord nr0 =
+ generateNotificationRecord(mTestNotificationChannel, 0, USER_SYSTEM);
+ NotificationRecord nr1 =
+ generateNotificationRecord(mTestNotificationChannel, 1, USER_SYSTEM);
+
+ // add notifications + summary for USER_SYSTEM
+ mService.addNotification(nr0);
+ mService.addNotification(nr1);
+ mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
+ nr1.getSbn().getPackageName(), nr1.getKey(), GroupHelper.BASE_FLAGS));
+
+ // add notifications + summary for USER_ALL
+ NotificationRecord nr0_all =
+ generateNotificationRecord(mTestNotificationChannel, 2, UserHandle.USER_ALL);
+ NotificationRecord nr1_all =
+ generateNotificationRecord(mTestNotificationChannel, 3, UserHandle.USER_ALL);
+
+ mService.addNotification(nr0_all);
+ mService.addNotification(nr1_all);
+ mService.addNotification(mService.createAutoGroupSummary(nr0_all.getUserId(),
+ nr0_all.getSbn().getPackageName(), nr0_all.getKey(), GroupHelper.BASE_FLAGS));
+
+ // cancel both children for USER_ALL
+ mBinderService.cancelNotificationWithTag(PKG, PKG, nr0_all.getSbn().getTag(),
+ nr0_all.getSbn().getId(), UserHandle.USER_ALL);
+ mBinderService.cancelNotificationWithTag(PKG, PKG, nr1_all.getSbn().getTag(),
+ nr1_all.getSbn().getId(), UserHandle.USER_ALL);
+ waitForIdle();
+
+ // group helper would send 'remove summary' event
+ mService.clearAutogroupSummaryLocked(UserHandle.USER_ALL,
+ nr0_all.getSbn().getPackageName());
+ waitForIdle();
+
+ // make sure the right summary was removed
+ assertThat(mService.getNotificationCount(nr0_all.getSbn().getPackageName(),
+ UserHandle.USER_ALL, 0, null)).isEqualTo(0);
+
+ // the USER_SYSTEM notifications + summary were not removed
+ assertThat(mService.getNotificationCount(nr0.getSbn().getPackageName(),
+ USER_SYSTEM, 0, null)).isEqualTo(3);
+ }
+
+ @Test
public void testStrongAuthTracker_isInLockDownMode() {
mStrongAuthTracker.setGetStrongAuthForUserReturnValue(
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
@@ -12047,6 +12124,70 @@
assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old
}
+ @Test
+ public void enqueueNotification_allowlistsPendingIntents() throws RemoteException {
+ PendingIntent contentIntent = createPendingIntent("content");
+ PendingIntent actionIntent1 = createPendingIntent("action1");
+ PendingIntent actionIntent2 = createPendingIntent("action2");
+ Notification n = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentIntent(contentIntent)
+ .addAction(new Notification.Action.Builder(null, "action1", actionIntent1).build())
+ .addAction(new Notification.Action.Builder(null, "action2", actionIntent2).build())
+ .build();
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 1,
+ parcelAndUnparcel(n, Notification.CREATOR), mUserId);
+
+ verify(mAmi, times(3)).setPendingIntentAllowlistDuration(
+ any(), any(), anyLong(),
+ eq(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED),
+ eq(REASON_NOTIFICATION_SERVICE), any());
+ verify(mAmi, times(3)).setPendingIntentAllowBgActivityStarts(any(),
+ any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+ }
+
+ @Test
+ public void enqueueNotification_allowlistsPendingIntents_includingFromPublicVersion()
+ throws RemoteException {
+ PendingIntent contentIntent = createPendingIntent("content");
+ PendingIntent actionIntent = createPendingIntent("action");
+ PendingIntent publicContentIntent = createPendingIntent("publicContent");
+ PendingIntent publicActionIntent = createPendingIntent("publicAction");
+ Notification source = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentIntent(contentIntent)
+ .addAction(new Notification.Action.Builder(null, "action", actionIntent).build())
+ .setPublicVersion(new Notification.Builder(mContext, "channel")
+ .setContentIntent(publicContentIntent)
+ .addAction(new Notification.Action.Builder(
+ null, "publicAction", publicActionIntent).build())
+ .build())
+ .build();
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 1,
+ parcelAndUnparcel(source, Notification.CREATOR), mUserId);
+
+ verify(mAmi, times(4)).setPendingIntentAllowlistDuration(
+ any(), any(), anyLong(),
+ eq(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED),
+ eq(REASON_NOTIFICATION_SERVICE), any());
+ verify(mAmi, times(4)).setPendingIntentAllowBgActivityStarts(any(),
+ any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+ }
+
+ private static <T extends Parcelable> T parcelAndUnparcel(T source,
+ Parcelable.Creator<T> creator) {
+ Parcel parcel = Parcel.obtain();
+ source.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return creator.createFromParcel(parcel);
+ }
+
+ private PendingIntent createPendingIntent(String action) {
+ return PendingIntent.getActivity(mContext, 0,
+ new Intent(action).setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
+ }
+
private void setDpmAppOppsExemptFromDismissal(boolean isOn) {
DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index d32289d..121e296 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -67,6 +67,8 @@
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -88,7 +90,6 @@
// This list should be emptied! Items can be removed as bugs are fixed.
private static final Multimap<Class<?>, String> KNOWN_BAD =
ImmutableMultimap.<Class<?>, String>builder()
- .put(Notification.WearableExtender.class, "addAction") // TODO: b/281044385
.put(Person.Builder.class, "setUri") // TODO: b/281044385
.put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385
.build();
@@ -164,7 +165,7 @@
/* extenderClass= */ Notification.WearableExtender.class,
/* actionExtenderClass= */ Notification.Action.WearableExtender.class,
/* includeRemoteViews= */ true);
- assertThat(notification.includedUris.size()).isAtLeast(730);
+ assertThat(notification.includedUris.size()).isAtLeast(900);
}
@Test
@@ -450,19 +451,43 @@
Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
Log.i(TAG, "About to generate parameters for " + ReflectionUtils.methodToString(executable)
+ " in " + where);
- Class<?>[] parameterTypes = executable.getParameterTypes();
+ Type[] parameterTypes = executable.getGenericParameterTypes();
Object[] parameterValues = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
- parameterValues[i] = generateObject(
+ parameterValues[i] = generateParameter(
parameterTypes[i],
where.plus(executable,
- String.format("[%d,%s]", i, parameterTypes[i].getName())),
+ String.format("[%d,%s]", i, parameterTypes[i].getTypeName())),
excludingClasses,
specialGenerator);
}
return parameterValues;
}
+ private static Object generateParameter(Type parameterType, Location where,
+ Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+ if (parameterType instanceof Class<?> parameterClass) {
+ return generateObject(
+ parameterClass,
+ where,
+ excludingClasses,
+ specialGenerator);
+ } else if (parameterType instanceof ParameterizedType parameterizedType) {
+ if (parameterizedType.getRawType().equals(List.class)
+ && parameterizedType.getActualTypeArguments()[0] instanceof Class<?>) {
+ ArrayList listValue = new ArrayList();
+ for (int i = 0; i < NUM_ELEMENTS_IN_ARRAY; i++) {
+ listValue.add(
+ generateObject((Class<?>) parameterizedType.getActualTypeArguments()[0],
+ where, excludingClasses, specialGenerator));
+ }
+ return listValue;
+ }
+ }
+ throw new IllegalArgumentException(
+ "I have no idea how to produce a(n) " + parameterType + ", sorry");
+ }
+
private static class ReflectionUtils {
static Set<Class<?>> getConcreteSubclasses(Class<?> clazz, Class<?> containerClass) {
return Arrays.stream(containerClass.getDeclaredClasses())
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
index 6a1674b..63b8e17 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
@@ -38,13 +38,16 @@
import android.os.Process;
import android.os.RemoteException;
+import androidx.test.filters.FlakyTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.FakeLatencyTracker;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
@@ -52,8 +55,12 @@
import org.mockito.MockitoAnnotations;
@RunWith(JUnit4.class)
+@FlakyTest(bugId = 275746222)
public class SoundTriggerMiddlewareLoggingLatencyTest {
+ @Rule
+ public Timeout mGlobalTimeout = Timeout.seconds(30);
+
private FakeLatencyTracker mLatencyTracker;
@Mock
private BatteryStatsInternal mBatteryStatsInternal;
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
index 280afba..3bb86a7 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -316,4 +316,16 @@
pressKey(KEYCODE_POWER, 0 /* pressTime */);
assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
}
+
+ // Verify short press should not be triggered if no very long press behavior defined but the
+ // press time exceeded the very long press timeout.
+ @Test
+ public void testTimeoutExceedVeryLongPress() throws InterruptedException {
+ mVeryLongPressOnPowerBehavior = false;
+
+ pressKey(KEYCODE_POWER, mVeryLongPressTime + 50);
+ assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
+ assertEquals(mVeryLongPressed.getCount(), 1);
+ assertEquals(mShortPressed.getCount(), 1);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 5369b93..bdd178b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2046,6 +2046,7 @@
// Once transition starts, rotation is applied and transition shows DC rotating.
testPlayer.startTransition();
+ waitUntilHandlersIdle();
assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
assertNotNull(testPlayer.mLastReady);
assertTrue(testPlayer.mController.isPlaying());
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 204cbf7..994dcf1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -31,17 +31,12 @@
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
import android.app.StatusBarManager;
@@ -268,8 +263,6 @@
navBar.setHasSurface(true);
navBarProvider.setServerVisible(true);
final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
- spyOn(policy);
- doNothing().when(policy).startAnimation(anyBoolean(), any());
// Make both system bars invisible.
mAppWindow.setRequestedVisibleTypes(
@@ -305,8 +298,6 @@
addNavigationBar().getControllableInsetProvider().setServerVisible(true);
final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
- spyOn(policy);
- doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(mAppWindow);
policy.showTransient(navigationBars() | statusBars(),
true /* isGestureOnSystemBar */);
@@ -341,8 +332,6 @@
mAppWindow.mAboveInsetsState.addSource(navBarSource);
mAppWindow.mAboveInsetsState.addSource(statusBarSource);
final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
- spyOn(policy);
- doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(mAppWindow);
policy.showTransient(navigationBars() | statusBars(),
true /* isGestureOnSystemBar */);
@@ -390,8 +379,6 @@
final WindowState app2 = addWindow(TYPE_APPLICATION, "app");
final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
- spyOn(policy);
- doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(app);
policy.showTransient(navigationBars() | statusBars(),
true /* isGestureOnSystemBar */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 7cb7c79d..2b19ad9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -107,6 +107,9 @@
InstrumentationRegistry.getInstrumentation().getContext().getCacheDir();
mFolder = new File(cacheFolder, "launch_params_tests");
deleteRecursively(mFolder);
+ mFolder.mkdir();
+ mUserFolderGetter.apply(TEST_USER_ID).mkdir();
+ mUserFolderGetter.apply(ALTERNATIVE_USER_ID).mkdir();
mDisplayUniqueId = "test:" + sNextUniqueId++;
mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500)
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 4639ee0..45ecc3f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -77,6 +77,10 @@
spyOn(inputMonitor);
doNothing().when(inputMonitor).resumeDispatchingLw(any());
+ final InsetsPolicy insetsPolicy = getInsetsPolicy();
+ WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget());
+ WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getTransientControlTarget());
+
// For devices that set the sysprop ro.bootanim.set_orientation_<display_id>
// See DisplayRotation#readDefaultDisplayRotation for context.
// Without that, meaning of height and width in context of the tests can be swapped if
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index adf3f39..bd111ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -233,7 +233,7 @@
}
@Override
- public void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition) {
+ public void onKeyguardOccludedChangedLw(boolean occluded) {
}
public void setSafeMode(boolean safeMode) {
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 2d8ddfa..a82459f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -64,10 +64,19 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.clearInvocations;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Binder;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.platform.test.annotations.Presubmit;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
@@ -87,8 +96,10 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
+import java.io.FileDescriptor;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.NoSuchElementException;
/**
@@ -1404,7 +1415,7 @@
}
@Test
- public void testAddLocalInsetsSourceProvider() {
+ public void testAddLocalInsetsFrameProvider() {
/*
___ rootTask _______________________________________________
| | | |
@@ -1435,19 +1446,20 @@
TYPE_BASE_APPLICATION);
attrs2.setTitle("AppWindow2");
activity2.addWindow(createWindowState(attrs2, activity2));
+ final Binder owner = new Binder();
Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200);
final InsetsFrameProvider provider1 =
- new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+ new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
.setArbitraryRectangle(genericOverlayInsetsRect1);
final InsetsFrameProvider provider2 =
- new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays())
+ new InsetsFrameProvider(owner, 2, WindowInsets.Type.systemOverlays())
.setArbitraryRectangle(genericOverlayInsetsRect2);
final int sourceId1 = provider1.getId();
final int sourceId2 = provider2.getId();
- rootTask.addLocalInsetsFrameProvider(provider1);
- container.addLocalInsetsFrameProvider(provider2);
+ rootTask.addLocalInsetsFrameProvider(provider1, owner);
+ container.addLocalInsetsFrameProvider(provider2, owner);
InsetsSource genericOverlayInsetsProvider1Source = new InsetsSource(
sourceId1, systemOverlays());
@@ -1479,7 +1491,7 @@
}
@Test
- public void testAddLocalInsetsSourceProvider_sameType_replacesInsets() {
+ public void testAddLocalInsetsFrameProvider_sameType_replacesInsets() {
/*
___ rootTask ________________________________________
| | |
@@ -1494,24 +1506,25 @@
attrs.setTitle("AppWindow0");
activity0.addWindow(createWindowState(attrs, activity0));
+ final Binder owner = new Binder();
final Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
final Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200);
final InsetsFrameProvider provider1 =
- new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+ new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
.setArbitraryRectangle(genericOverlayInsetsRect1);
final InsetsFrameProvider provider2 =
- new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+ new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
.setArbitraryRectangle(genericOverlayInsetsRect2);
final int sourceId1 = provider1.getId();
final int sourceId2 = provider2.getId();
- rootTask.addLocalInsetsFrameProvider(provider1);
+ rootTask.addLocalInsetsFrameProvider(provider1, owner);
activity0.forAllWindows(window -> {
assertEquals(genericOverlayInsetsRect1,
window.getInsetsState().peekSource(sourceId1).getFrame());
}, true);
- rootTask.addLocalInsetsFrameProvider(provider2);
+ rootTask.addLocalInsetsFrameProvider(provider2, owner);
activity0.forAllWindows(window -> {
assertEquals(genericOverlayInsetsRect2,
@@ -1520,7 +1533,7 @@
}
@Test
- public void testRemoveLocalInsetsSourceProvider() {
+ public void testRemoveLocalInsetsFrameProvider() {
/*
___ rootTask _______________________________________________
| | | |
@@ -1554,21 +1567,22 @@
activity2.addWindow(createWindowState(attrs2, activity2));
+ final Binder owner = new Binder();
final Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700);
final Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
final InsetsFrameProvider provider1 =
- new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+ new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
.setArbitraryRectangle(navigationBarInsetsRect1);
final InsetsFrameProvider provider2 =
- new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays())
+ new InsetsFrameProvider(owner, 2, WindowInsets.Type.systemOverlays())
.setArbitraryRectangle(navigationBarInsetsRect2);
final int sourceId1 = provider1.getId();
final int sourceId2 = provider2.getId();
- rootTask.addLocalInsetsFrameProvider(provider1);
- container.addLocalInsetsFrameProvider(provider2);
+ rootTask.addLocalInsetsFrameProvider(provider1, owner);
+ container.addLocalInsetsFrameProvider(provider2, owner);
mDisplayContent.getInsetsStateController().onPostLayout();
- rootTask.removeLocalInsetsFrameProvider(provider1);
+ rootTask.removeLocalInsetsFrameProvider(provider1, owner);
mDisplayContent.getInsetsStateController().onPostLayout();
activity0.forAllWindows(window -> {
@@ -1593,6 +1607,67 @@
}, true);
}
+ @Test
+ public void testAddLocalInsetsFrameProvider_ownerDiesAfterAdding() {
+ final Task task = createTask(mDisplayContent);
+ final TestBinder owner = new TestBinder();
+ final InsetsFrameProvider provider =
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays())
+ .setArbitraryRectangle(new Rect());
+ task.addLocalInsetsFrameProvider(provider, owner);
+
+ assertTrue("The death recipient must exist.", owner.hasDeathRecipient());
+ assertTrue("The source must be added.", hasLocalSource(task, provider.getId()));
+
+ // The owner dies after adding the source.
+ owner.die();
+
+ assertFalse("The death recipient must be removed.", owner.hasDeathRecipient());
+ assertFalse("The source must be removed.", hasLocalSource(task, provider.getId()));
+ }
+
+ @Test
+ public void testAddLocalInsetsFrameProvider_ownerDiesBeforeAdding() {
+ final Task task = createTask(mDisplayContent);
+ final TestBinder owner = new TestBinder();
+
+ // The owner dies before adding the source.
+ owner.die();
+
+ final InsetsFrameProvider provider =
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays())
+ .setArbitraryRectangle(new Rect());
+ task.addLocalInsetsFrameProvider(provider, owner);
+
+ assertFalse("The death recipient must not exist.", owner.hasDeathRecipient());
+ assertFalse("The source must not be added.", hasLocalSource(task, provider.getId()));
+ }
+
+ @Test
+ public void testRemoveLocalInsetsFrameProvider_removeDeathRecipient() {
+ final Task task = createTask(mDisplayContent);
+ final TestBinder owner = new TestBinder();
+ final InsetsFrameProvider provider =
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays())
+ .setArbitraryRectangle(new Rect());
+ task.addLocalInsetsFrameProvider(provider, owner);
+
+ assertTrue("The death recipient must exist.", owner.hasDeathRecipient());
+ assertTrue("The source must be added.", hasLocalSource(task, provider.getId()));
+
+ task.removeLocalInsetsFrameProvider(provider, owner);
+
+ assertFalse("The death recipient must be removed.", owner.hasDeathRecipient());
+ assertFalse("The source must be removed.", hasLocalSource(task, provider.getId()));
+ }
+
+ private static boolean hasLocalSource(WindowContainer container, int sourceId) {
+ if (container.mLocalInsetsSources == null) {
+ return false;
+ }
+ return container.mLocalInsetsSources.contains(sourceId);
+ }
+
/* 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;
@@ -1797,4 +1872,82 @@
mIsVisibleRequested = isVisibleRequested;
}
}
+
+ private static class TestBinder implements IBinder {
+
+ private boolean mDead;
+ private final ArrayList<IBinder.DeathRecipient> mDeathRecipients = new ArrayList<>();
+
+ public void die() {
+ mDead = true;
+ for (int i = mDeathRecipients.size() - 1; i >= 0; i--) {
+ final DeathRecipient recipient = mDeathRecipients.get(i);
+ recipient.binderDied(this);
+ }
+ }
+
+ public boolean hasDeathRecipient() {
+ return !mDeathRecipients.isEmpty();
+ }
+
+ @Override
+ public void linkToDeath(@NonNull DeathRecipient recipient, int flags)
+ throws RemoteException {
+ if (mDead) {
+ throw new DeadObjectException();
+ }
+ mDeathRecipients.add(recipient);
+ }
+
+ @Override
+ public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
+ final boolean successes = mDeathRecipients.remove(recipient);
+ if (successes || mDead) {
+ return successes;
+ }
+ throw new NoSuchElementException("Given recipient has not been registered.");
+ }
+
+ @Override
+ public boolean isBinderAlive() {
+ return !mDead;
+ }
+
+ @Override
+ public boolean pingBinder() {
+ return !mDead;
+ }
+
+ @Nullable
+ @Override
+ public String getInterfaceDescriptor() throws RemoteException {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public IInterface queryLocalInterface(@NonNull String descriptor) {
+ return null;
+ }
+
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @Nullable String[] args)
+ throws RemoteException { }
+
+ @Override
+ public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args)
+ throws RemoteException { }
+
+ @Override
+ public void shellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err, @NonNull String[] args,
+ @Nullable ShellCallback shellCallback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException { }
+
+ @Override
+ public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
+ throws RemoteException {
+ return false;
+ }
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 197ee92..64330d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -81,6 +81,7 @@
import android.platform.test.annotations.Presubmit;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
+import android.view.ContentRecordingSession;
import android.view.IWindow;
import android.view.IWindowSessionCallback;
import android.view.InputChannel;
@@ -101,6 +102,7 @@
import com.android.compatibility.common.util.AdoptShellPermissionsRule;
import com.android.internal.os.IResultReceiver;
+import com.android.server.LocalServices;
import org.junit.Rule;
import org.junit.Test;
@@ -761,6 +763,63 @@
}
@Test
+ public void setContentRecordingSession_sessionNull_returnsTrue() {
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+
+ boolean result = wmInternal.setContentRecordingSession(/* incomingSession= */ null);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void setContentRecordingSession_sessionContentDisplay_returnsTrue() {
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
+ DEFAULT_DISPLAY);
+
+ boolean result = wmInternal.setContentRecordingSession(session);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void setContentRecordingSession_sessionContentTask_noMatchingTask_returnsFalse() {
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ IBinder launchCookie = new Binder();
+ ContentRecordingSession session = ContentRecordingSession.createTaskSession(launchCookie);
+
+ boolean result = wmInternal.setContentRecordingSession(session);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void setContentRecordingSession_sessionContentTask_matchingTask_returnsTrue() {
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ ActivityRecord activityRecord = createActivityRecord(createTask(mDefaultDisplay));
+ ContentRecordingSession session = ContentRecordingSession.createTaskSession(
+ activityRecord.mLaunchCookie);
+
+ boolean result = wmInternal.setContentRecordingSession(session);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() {
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ Task task = createTask(mDefaultDisplay);
+ ActivityRecord activityRecord = createActivityRecord(task);
+ ContentRecordingSession session = ContentRecordingSession.createTaskSession(
+ activityRecord.mLaunchCookie);
+
+ wmInternal.setContentRecordingSession(session);
+
+ assertThat(session.getTokenToRecord()).isEqualTo(
+ task.mRemoteToken.toWindowContainerToken().asBinder());
+ }
+
+ @Test
public void testisLetterboxBackgroundMultiColored() {
assertThat(setupLetterboxConfigurationWithBackgroundType(
LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index be8ee78..d5547ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -222,6 +222,10 @@
displayPolicy.finishWindowsDrawn();
displayPolicy.finishScreenTurningOn();
+ final InsetsPolicy insetsPolicy = mDefaultDisplay.getInsetsPolicy();
+ suppressInsetsAnimation(insetsPolicy.getTransientControlTarget());
+ suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget());
+
mTransaction = mSystemServicesTestRule.mTransaction;
mMockSession = mock(Session.class);
@@ -278,6 +282,15 @@
checkDeviceSpecificOverridesNotApplied();
}
+ /**
+ * The test doesn't create real SurfaceControls, but mocked ones. This prevents the target from
+ * controlling them, or it will cause {@link NullPointerException}.
+ */
+ static void suppressInsetsAnimation(InsetsControlTarget target) {
+ spyOn(target);
+ Mockito.doNothing().when(target).notifyInsetsControlChanged();
+ }
+
@After
public void tearDown() throws Exception {
if (mUseFakeSettingsProvider) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index dc5b75a..3e79193 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -50,7 +50,6 @@
import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.feature.RcsFeature;
-import android.telephony.ims.stub.ImsRegistrationImplBase;
import com.android.internal.telephony.ICarrierConfigLoader;
import com.android.telephony.Rlog;
@@ -5813,6 +5812,21 @@
*/
public static final int NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED = 3;
+ /**
+ * This specifies whether the carrier support the global number format or not.
+ * {@link SubscriptionManager#getPhoneNumber(int)},
+ * {@link SubscriptionManager#getPhoneNumber(int, int)} with
+ * {@link SubscriptionManager#PHONE_NUMBER_SOURCE_IMS}
+ * In order to provide the phone number to the APIs, the framework extracts the phone
+ * number from the message received from the carrier server. If the carrier does not use
+ * global number format, the framework could not provide phone number.
+ * <p>
+ * If not set or set to false value, the framework handle only global number format URI.
+ * @hide
+ */
+ public static final String KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL =
+ KEY_PREFIX + "allow_non_global_phone_number_format_bool";
+
private Ims() {}
private static PersistableBundle getDefaults() {
@@ -5925,6 +5939,8 @@
defaults.putString(KEY_IMS_USER_AGENT_STRING,
"#MANUFACTURER#_#MODEL#_Android#AV#_#BUILD#");
+ defaults.putBoolean(KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL, false);
+
return defaults;
}
}
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
index db36975..346622f 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -61,8 +61,11 @@
options.setTestMethodName("testCollectAllApexInfo");
// Collect APEX package names from /apex, then pass them as expectation to be verified.
+ // The package names are collected from the find name with deduplication (NB: we used to
+ // deduplicate by dropping directory names with '@', but there's a DCLA case where it only
+ // has one directory with '@'. So we have to keep it and deduplicate the current way).
CommandResult result = getDevice().executeShellV2Command(
- "ls -d /apex/*/ |grep -v @ |grep -v /apex/sharedlibs |cut -d/ -f3");
+ "ls -d /apex/*/ |grep -v /apex/sharedlibs |cut -d/ -f3 |cut -d@ -f1 |sort |uniq");
assertTrue(result.getStatus() == CommandStatus.SUCCESS);
String[] packageNames = result.getStdout().split("\n");
for (var i = 0; i < packageNames.length; i++) {
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 8faedeb..72b5159 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -37,6 +37,7 @@
name: "FlickerTestsActivityEmbedding-src",
srcs: [
"src/**/activityembedding/*.kt",
+ "src/**/activityembedding/open/*.kt",
"src/**/activityembedding/close/*.kt",
"src/**/activityembedding/rotation/*.kt",
],
@@ -67,6 +68,13 @@
srcs: ["src/**/rotation/*.kt"],
}
+filegroup {
+ name: "FlickerServiceTests-src",
+ srcs: [
+ "src/com/android/server/wm/flicker/service/**/*.kt",
+ ],
+}
+
java_defaults {
name: "FlickerTestsDefault",
manifest: "manifests/AndroidManifest.xml",
@@ -108,6 +116,8 @@
":FlickerTestsAppLaunch-src",
":FlickerTestsQuickswitch-src",
":FlickerTestsRotation-src",
+ ":FlickerTestsNotification-src",
+ ":FlickerServiceTests-src",
],
}
@@ -148,6 +158,9 @@
":FlickerTestsBase-src",
":FlickerTestsAppLaunch-src",
],
+ exclude_srcs: [
+ ":FlickerTestsActivityEmbedding-src",
+ ],
}
android_test {
@@ -189,6 +202,18 @@
],
}
+android_test {
+ name: "FlickerServiceTests",
+ defaults: ["FlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestService.xml"],
+ package_name: "com.android.server.wm.flicker.service",
+ instrumentation_target_package: "com.android.server.wm.flicker.service",
+ srcs: [
+ ":FlickerTestsBase-src",
+ ":FlickerServiceTests-src",
+ ],
+}
+
java_library {
name: "wm-flicker-common-assertions",
platform_apis: true,
diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml
index 1ede943..44a8245 100644
--- a/tests/FlickerTests/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -85,6 +85,10 @@
value="/data/user/0/com.android.server.wm.flicker.quickswitch/files"/>
<option name="directory-keys"
value="/data/user/0/com.android.server.wm.flicker.rotation/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.server.wm.flicker.notification/files"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.server.wm.flicker.service/files"/>
<option name="collect-on-run-ended-only" value="true"/>
<option name="clean-up" value="true"/>
</metrics_collector>
diff --git a/tests/FlickerTests/manifests/AndroidManifestService.xml b/tests/FlickerTests/manifests/AndroidManifestService.xml
new file mode 100644
index 0000000..3a7bc509
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestService.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.wm.flicker.service">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.wm.flicker.service"
+ android:label="WindowManager Flicker Service Tests">
+ </instrumentation>
+</manifest>
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 1fdbe7f..b18a1a8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -255,20 +255,18 @@
invoke("snapshotStartingWindowLayerCoversExactlyOnApp") {
val snapshotLayers =
it.subjects.filter { subject ->
- subject.name.contains(ComponentNameMatcher.SNAPSHOT.toLayerName()) &&
+ ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(subject.layer) &&
subject.isVisible
}
+ val visibleAreas =
+ snapshotLayers
+ .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion }
+ .toTypedArray()
+ val snapshotRegion = RegionSubject(visibleAreas, timestamp)
// Verify the size of snapshotRegion covers appVisibleRegion exactly in animation.
- if (snapshotLayers.isNotEmpty()) {
- val visibleAreas =
- snapshotLayers
- .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion }
- .toTypedArray()
- val snapshotRegion = RegionSubject(visibleAreas, timestamp)
+ if (snapshotRegion.region.isNotEmpty) {
val appVisibleRegion = it.visibleRegion(component)
- if (snapshotRegion.region.isNotEmpty) {
- snapshotRegion.coversExactly(appVisibleRegion.region)
- }
+ snapshotRegion.coversExactly(appVisibleRegion.region)
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
index 7a582f7..4530ef3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.activityembedding
+package com.android.server.wm.flicker.activityembedding.close
import android.platform.test.annotations.Presubmit
import android.tools.common.datatypes.Rect
@@ -24,6 +24,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
index 48aaebd..0f406fd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.activityembedding
+package com.android.server.wm.flicker.activityembedding.open
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
@@ -24,8 +24,10 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -54,7 +56,7 @@
testApp.launchViaIntent(wmHelper)
testApp.launchSecondaryActivity(wmHelper)
startDisplayBounds =
- wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+ wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
}
transitions {
// Launch C with alwaysExpand
@@ -66,23 +68,14 @@
}
}
- @FlakyTest(bugId = 286952194)
- @Presubmit
- @Test
- override fun navBarWindowIsVisibleAtStartAndEnd() {}
+ @Ignore("Not applicable to this CUJ.") override fun navBarWindowIsVisibleAtStartAndEnd() {}
- @FlakyTest(bugId = 286952194)
- @Presubmit
- @Test
- override fun statusBarWindowIsAlwaysVisible() {}
+ @Ignore("Not applicable to this CUJ.") override fun statusBarWindowIsAlwaysVisible() {}
- @FlakyTest(bugId = 286952194)
- @Presubmit
- @Test
- override fun statusBarLayerPositionAtStartAndEnd() {}
+ @Ignore("Not applicable to this CUJ.") override fun statusBarLayerPositionAtStartAndEnd() {}
/** Transition begins with a split. */
- @Presubmit
+ @FlakyTest(bugId = 286952194)
@Test
fun startsWithSplit() {
flicker.assertWmStart {
@@ -94,7 +87,7 @@
}
/** Main activity should become invisible after being covered by always expand activity. */
- @Presubmit
+ @FlakyTest(bugId = 286952194)
@Test
fun mainActivityLayerBecomesInvisible() {
flicker.assertLayers {
@@ -105,7 +98,7 @@
}
/** Secondary activity should become invisible after being covered by always expand activity. */
- @Presubmit
+ @FlakyTest(bugId = 286952194)
@Test
fun secondaryActivityLayerBecomesInvisible() {
flicker.assertLayers {
@@ -116,7 +109,7 @@
}
/** At the end of transition always expand activity is in fullscreen. */
- @Presubmit
+ @FlakyTest(bugId = 286952194)
@Test
fun endsWithAlwaysExpandActivityCoveringFullScreen() {
flicker.assertWmEnd {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
index 27a5bd0..8a997dd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.activityembedding
+package com.android.server.wm.flicker.activityembedding.open
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -22,6 +22,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
index 16dbfce..49aa84b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.activityembedding
+package com.android.server.wm.flicker.activityembedding.open
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
@@ -23,6 +23,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
new file mode 100644
index 0000000..27de12e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 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.activityembedding.open
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a secondary activity over an existing split. By default the new secondary activity
+ * will stack over the previous secondary activity.
+ *
+ * Setup: From Activity A launch a split A|B.
+ *
+ * Transitions: Let B start C, expect C to cover B and end up in split A|C.
+ *
+ * To run this test: `atest FlickerTests:OpenThirdActivityOverSplitTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenThirdActivityOverSplitTest(flicker: LegacyFlickerTest) :
+ ActivityEmbeddingTestBase(flicker) {
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit = {
+ setup {
+ tapl.setExpectedRotationCheckEnabled(false)
+ // Launch a split.
+ testApp.launchViaIntent(wmHelper)
+ testApp.launchSecondaryActivity(wmHelper)
+
+ startDisplayBounds =
+ wmHelper.currentState.layerState.physicalDisplayBounds
+ ?: error("Can't get display bounds")
+ }
+ transitions { testApp.launchThirdActivity(wmHelper) }
+ teardown {
+ tapl.goHome()
+ testApp.exit(wmHelper)
+ }
+ }
+
+ /** Main activity remains visible throughout the transition. */
+ @Presubmit
+ @Test
+ fun mainActivityWindowAlwaysVisible() {
+ flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+ }
+
+ /** Main activity remains visible throughout the transition and takes up half of the screen. */
+ @Presubmit
+ @Test
+ fun mainActivityLayersAlwaysVisible() {
+ flicker.assertLayers { isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+
+ flicker.assertLayersStart {
+ val display =
+ this.entry.displays.firstOrNull { it.isOn && !it.isVirtual }
+ ?: error("No non-virtual and on display found")
+ val mainActivityRegion =
+ this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ val secondaryActivityRegion =
+ this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT).region
+ mainActivityRegion.plus(secondaryActivityRegion).coversExactly(display.layerStackSpace)
+ }
+
+ flicker.assertLayersEnd {
+ val display =
+ this.entry.displays.firstOrNull { it.isOn && !it.isVirtual }
+ ?: error("No non-virtual and on display found")
+ val mainActivityRegion =
+ this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ val secondaryActivityRegion =
+ this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ secondaryActivityRegion.isEmpty()
+ val thirdActivityRegion =
+ this.visibleRegion(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+ mainActivityRegion
+ .plus(thirdActivityRegion.region)
+ .coversExactly(display.layerStackSpace)
+ }
+ }
+
+ /** Third activity launches during the transition and covers up secondary activity. */
+ @Presubmit
+ @Test
+ fun thirdActivityWindowLaunchesIntoSplit() {
+ flicker.assertWm {
+ isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .isAppWindowInvisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+ .then()
+ .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .isAppWindowVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+ .then()
+ .isAppWindowVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+ .isAppWindowInvisible(
+ ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT
+ ) // expectation
+ }
+ }
+
+ /** Third activity launches during the transition and covers up secondary activity. */
+ @Presubmit
+ @Test
+ fun thirdActivityLayerLaunchesIntoSplit() {
+ flicker.assertLayers {
+ isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .isInvisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+ .then()
+ .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .isVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+ .then()
+ .isVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+ .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** Assert the background animation layer is never visible during transition. */
+ @Presubmit
+ @Test
+ fun backgroundLayerNeverVisible() {
+ val backgroundColorLayer = ComponentNameMatcher("", "Animation Background")
+ flicker.assertLayers { isInvisible(backgroundColorLayer) }
+ }
+
+ companion object {
+ /** {@inheritDoc} */
+ private var startDisplayBounds = Rect.EMPTY
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
index 856c9e2..da56500 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.activityembedding
+package com.android.server.wm.flicker.activityembedding.rotation
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
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 865d5b4..dbbc771 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,7 @@
package com.android.server.wm.flicker.close
import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
index c108633..566f393 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
@@ -16,7 +16,7 @@
package com.android.server.wm.flicker.close
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
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 ea9710c6..ed930fc 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
@@ -17,7 +17,7 @@
package com.android.server.wm.flicker.close
import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
index d65555a..49ed183 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
@@ -16,7 +16,7 @@
package com.android.server.wm.flicker.close
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index 351eb1e..ced7a1e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -59,6 +59,23 @@
.waitForAndVerify()
}
+ /** Clicks the button to launch a third activity over a secondary activity. */
+ fun launchThirdActivity(wmHelper: WindowManagerStateHelper) {
+ val launchButton =
+ uiDevice.wait(
+ Until.findObject(By.res(getPackage(), "launch_third_activity_button")),
+ FIND_TIMEOUT
+ )
+ require(launchButton != null) { "Can't find launch third activity button on screen." }
+ launchButton.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+ .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_STOPPED)
+ .withActivityState(THIRD_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+ .waitForAndVerify()
+ }
+
/**
* Clicks the button to finishes the secondary activity launched through
* [launchSecondaryActivity], waits for the main activity to resume.
@@ -166,6 +183,9 @@
val SECONDARY_ACTIVITY_COMPONENT =
ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent()
+ val THIRD_ACTIVITY_COMPONENT =
+ ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT.toFlickerComponent()
+
val ALWAYS_EXPAND_ACTIVITY_COMPONENT =
ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT.toFlickerComponent()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 24e231c..c975a50 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -250,10 +250,13 @@
waitConditions = arrayOf(ConditionsFactory.hasPipWindow())
)
+ val windowRegion = wmHelper.getWindowRegion(this)
+
wmHelper
.StateSyncBuilder()
.withWindowSurfaceAppeared(this)
.withPipShown()
+ .withSurfaceVisibleRegion(this, windowRegion)
.waitForAndVerify()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt
index 2563bfb..4fd4a61 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt
@@ -17,7 +17,7 @@
package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
index 33302fa..e39a578 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
@@ -18,7 +18,7 @@
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt
index 45fb453..6d0b6f4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt
@@ -17,7 +17,7 @@
package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
index 12ee7d0..d2c3807 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
@@ -18,7 +18,7 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt
index 1371fd7..3e0958a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt
@@ -16,7 +16,7 @@
package com.android.server.wm.flicker.launch
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
index 46eb257..7a16060 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -20,8 +20,8 @@
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
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 1497e50..eec6bfd 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
@@ -19,7 +19,7 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
index 9b6c136..ab6a1ea 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
@@ -16,7 +16,7 @@
package com.android.server.wm.flicker.launch
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
index 4a1bd7e..1bdb6e71 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
@@ -18,6 +18,8 @@
import android.os.SystemClock
import android.platform.test.annotations.Postsubmit
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.apphelpers.CameraAppHelper
import android.tools.device.apphelpers.StandardAppHelper
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -137,8 +139,14 @@
@Postsubmit
@Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ flicker.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +
+ listOf(CAMERA_BACKGROUND)
+ )
+ }
+ }
@Postsubmit
@Test
@@ -161,5 +169,12 @@
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+
+ private val CAMERA_BACKGROUND =
+ ComponentNameMatcher(
+ "Background for SurfaceView" +
+ "[com.google.android.GoogleCamera/" +
+ "com.google.android.apps.camera.legacy.app.activity.main.CameraActivity]"
+ )
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
index c336399..4e8a697 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
@@ -28,6 +28,7 @@
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -106,9 +107,9 @@
super.visibleLayersShownMoreThanOneConsecutiveEntry()
/** {@inheritDoc} */
- @FlakyTest(bugId = 209599395)
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+ @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+ override fun navBarLayerIsVisibleAtStartAndEnd() {}
/** {@inheritDoc} */
@Presubmit
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt
new file mode 100644
index 0000000..8242e9a
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 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.service
+
+import android.app.Instrumentation
+import android.platform.test.rule.NavigationModeRule
+import android.platform.test.rule.PressHomeRule
+import android.platform.test.rule.UnlockScreenRule
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.apphelpers.MessagingAppHelper
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.device.flicker.rules.LaunchAppRule
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.RuleChain
+
+object Utils {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ fun testSetupRule(navigationMode: NavBar, rotation: Rotation): RuleChain {
+ return RuleChain.outerRule(UnlockScreenRule())
+ .around(
+ NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ false)
+ )
+ .around(
+ LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false)
+ )
+ .around(RemoveAllTasksButHomeRule())
+ .around(
+ ChangeDisplayOrientationRule(
+ rotation,
+ resetOrientationAfterTest = false,
+ clearCacheAfterParsing = false
+ )
+ )
+ .around(PressHomeRule())
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
new file mode 100644
index 0000000..030b292
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.close.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButton3ButtonLandscape :
+ CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+ @Test
+ override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
new file mode 100644
index 0000000..770da143
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.close.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButton3ButtonPortrait :
+ CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+ @Test
+ override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
new file mode 100644
index 0000000..4b2206d7
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.close.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButtonGesturalNavLandscape :
+ CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+ @Test
+ override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
new file mode 100644
index 0000000..54b471e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.close.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButtonGesturalNavPortrait :
+ CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+ @Test
+ override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.kt
new file mode 100644
index 0000000..47c2529
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.service.close.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppHomeButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppHomeButton3ButtonLandscape : CloseAppHomeButton(Rotation.ROTATION_90) {
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+ @Test
+ override fun closeAppHomeButton() = super.closeAppHomeButton()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.kt
new file mode 100644
index 0000000..b18148f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.service.close.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppHomeButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppHomeButton3ButtonPortrait : CloseAppHomeButton(Rotation.ROTATION_0) {
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+ @Test
+ override fun closeAppHomeButton() = super.closeAppHomeButton()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..8543015
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.service.close.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppSwipeToHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppSwipeToHomeGesturalNavLandscape : CloseAppSwipeToHome(Rotation.ROTATION_90) {
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+ @Test
+ override fun closeAppSwipeToHome() = super.closeAppSwipeToHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..f88963b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.service.close.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppSwipeToHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppSwipeToHomeGesturalNavPortrait : CloseAppSwipeToHome(Rotation.ROTATION_0) {
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+ @Test
+ override fun closeAppSwipeToHome() = super.closeAppSwipeToHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt
new file mode 100644
index 0000000..2aaacde
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.server.wm.flicker.service.close.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CloseAppBackButton(
+ val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+ val rotation: Rotation = Rotation.ROTATION_0
+) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val testApp = SimpleAppHelper(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setExpectedRotation(rotation.value)
+ testApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun closeAppBackButtonTest() {
+ tapl.pressBack()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt
new file mode 100644
index 0000000..08683a3
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.server.wm.flicker.service.close.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CloseAppHomeButton(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val testApp = SimpleAppHelper(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_3BUTTON, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setExpectedRotation(rotation.value)
+ testApp.launchViaIntent(wmHelper)
+ tapl.setExpectedRotationCheckEnabled(false)
+ }
+
+ @Test
+ open fun closeAppHomeButton() {
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt
new file mode 100644
index 0000000..360e114
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.server.wm.flicker.service.close.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CloseAppSwipeToHome(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val testApp = SimpleAppHelper(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setExpectedRotation(rotation.value)
+ testApp.launchViaIntent(wmHelper)
+ tapl.setExpectedRotationCheckEnabled(false)
+ }
+
+ @Test
+ open fun closeAppSwipeToHome() {
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.kt
new file mode 100644
index 0000000..bb18770
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationCold3ButtonNavLandscape :
+ OpenAppFromLockscreenNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromLockscreenNotificationCold() =
+ super.openAppFromLockscreenNotificationCold()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.kt
new file mode 100644
index 0000000..1c3cc21
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationCold3ButtonNavPortrait :
+ OpenAppFromLockscreenNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromLockscreenNotificationCold() =
+ super.openAppFromLockscreenNotificationCold()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.kt
new file mode 100644
index 0000000..46d36db
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationColdGesturalNavLandscape :
+ OpenAppFromLockscreenNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromLockscreenNotificationCold() =
+ super.openAppFromLockscreenNotificationCold()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.kt
new file mode 100644
index 0000000..f6a668fe
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationColdGesturalNavPortrait :
+ OpenAppFromLockscreenNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromLockscreenNotificationCold() =
+ super.openAppFromLockscreenNotificationCold()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.kt
new file mode 100644
index 0000000..93200ba
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape :
+ OpenAppFromLockscreenNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromLockscreenNotificationWarm() =
+ super.openAppFromLockscreenNotificationWarm()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.kt
new file mode 100644
index 0000000..f5d41d2
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait :
+ OpenAppFromLockscreenNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromLockscreenNotificationWarm() =
+ super.openAppFromLockscreenNotificationWarm()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.kt
new file mode 100644
index 0000000..28f322b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarmGesturalNavLandscape :
+ OpenAppFromLockscreenNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromLockscreenNotificationWarm() =
+ super.openAppFromLockscreenNotificationWarm()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.kt
new file mode 100644
index 0000000..beb3fae
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarmGesturalNavPortrait :
+ OpenAppFromLockscreenNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromLockscreenNotificationWarm() =
+ super.openAppFromLockscreenNotificationWarm()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt
new file mode 100644
index 0000000..b34da72
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape :
+ OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromLockscreenNotificationWithOverlayApp() =
+ super.openAppFromLockscreenNotificationWithOverlayApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt
new file mode 100644
index 0000000..b163897
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait :
+ OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromLockscreenNotificationWithOverlayApp() =
+ super.openAppFromLockscreenNotificationWithOverlayApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt
new file mode 100644
index 0000000..19b533e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape :
+ OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromLockscreenNotificationWithOverlayApp() =
+ super.openAppFromLockscreenNotificationWithOverlayApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt
new file mode 100644
index 0000000..c9ed4f4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait :
+ OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromLockscreenNotificationWithOverlayApp() =
+ super.openAppFromLockscreenNotificationWithOverlayApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.kt
new file mode 100644
index 0000000..866190f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationCold3ButtonNavLandscape :
+ OpenAppFromNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.kt
new file mode 100644
index 0000000..a8d6283
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationCold3ButtonNavPortrait :
+ OpenAppFromNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.kt
new file mode 100644
index 0000000..ef84c3f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationColdGesturalNavLandscape :
+ OpenAppFromNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.kt
new file mode 100644
index 0000000..5bb6b37
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationColdGesturalNavPortrait :
+ OpenAppFromNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.kt
new file mode 100644
index 0000000..39f25e9
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarm3ButtonNavLandscape :
+ OpenAppFromNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.kt
new file mode 100644
index 0000000..28816bc
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarm3ButtonNavPortrait :
+ OpenAppFromNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.kt
new file mode 100644
index 0000000..94ffe4e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarmGesturalNavLandscape :
+ OpenAppFromNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.kt
new file mode 100644
index 0000000..e5f5ec4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarmGesturalNavPortrait :
+ OpenAppFromNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+ @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+ @Test
+ override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.kt
new file mode 100644
index 0000000..ac4e169
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 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.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.view.WindowInsets
+import android.view.WindowManager
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
+
+object NotificationUtils {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+
+ fun openNotification(openingNotificationsFromLockScreen: Boolean) {
+ var startY = 10
+ var endY = 3 * device.displayHeight / 4
+ var steps = 25
+ if (openingNotificationsFromLockScreen) {
+ val wm: WindowManager =
+ instrumentation.context.getSystemService(WindowManager::class.java)
+ ?: error("Unable to connect to WindowManager service")
+ val metricInsets = wm.currentWindowMetrics.windowInsets
+ val insets =
+ metricInsets.getInsetsIgnoringVisibility(
+ WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout()
+ )
+
+ startY = insets.top + 100
+ endY = device.displayHeight / 2
+ steps = 4
+ }
+
+ // Swipe down to show the notification shade
+ val x = device.displayWidth / 2
+ device.swipe(x, startY, x, endY, steps)
+ device.waitForIdle(2000)
+ instrumentation.uiAutomation.syncInputTransactions()
+
+ // Launch the activity by clicking the notification
+ val notification =
+ device.wait(Until.findObject(By.text("Flicker Test Notification")), 2000L)
+ notification?.click() ?: error("Notification not found")
+ instrumentation.uiAutomation.syncInputTransactions()
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt
new file mode 100644
index 0000000..9c53ab3
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 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.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromLockscreenNotificationCold(
+ val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+ val rotation: Rotation = Rotation.ROTATION_0
+) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+ val openingNotificationsFromLockScreen = true
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+ @Before
+ fun setup() {
+ device.wakeUpAndGoToHomeScreen()
+ testApp.launchViaIntent(wmHelper)
+ wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+ testApp.postNotification(wmHelper)
+ device.pressHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+ // Close the app that posted the notification to trigger a cold start next time
+ // it is open - can't just kill it because that would remove the notification.
+ tapl.setExpectedRotationCheckEnabled(false)
+ tapl.goHome()
+ tapl.workspace.switchToOverview()
+ tapl.overview.dismissAllTasks()
+
+ device.sleep()
+ wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+ }
+
+ @Test
+ open fun openAppFromLockscreenNotificationCold() {
+ device.wakeUp()
+
+ NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+ // Wait for the app to launch
+ wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.kt
new file mode 100644
index 0000000..31f55d9
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromLockscreenNotificationWarm(
+ val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+ val rotation: Rotation = Rotation.ROTATION_0
+) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+ private val openingNotificationsFromLockScreen = true
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+ @Before
+ fun setup() {
+ device.wakeUpAndGoToHomeScreen()
+ testApp.launchViaIntent(wmHelper)
+ wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+ testApp.postNotification(wmHelper)
+ device.pressHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+ device.sleep()
+ wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+ }
+
+ @Test
+ open fun openAppFromLockscreenNotificationWarm() {
+ device.wakeUp()
+
+ NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+ // Wait for the app to launch
+ wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.kt
new file mode 100644
index 0000000..b971555
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 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.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromLockscreenNotificationWithOverlayApp(
+ val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+ val rotation: Rotation = Rotation.ROTATION_0
+) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val showWhenLockedApp = ShowWhenLockedAppHelper(instrumentation)
+ private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+ // Although we are technically still locked here, the overlay app means we should open the
+ // notification shade as if we were unlocked.
+ private val openingNotificationsFromLockScreen = false
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+ @Before
+ fun setup() {
+ device.wakeUpAndGoToHomeScreen()
+ testApp.launchViaIntent(wmHelper)
+ wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+ testApp.postNotification(wmHelper)
+ device.pressHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+ // Close the app that posted the notification to trigger a cold start next time
+ // it is open - can't just kill it because that would remove the notification.
+ tapl.setExpectedRotationCheckEnabled(false)
+ tapl.goHome()
+ tapl.workspace.switchToOverview()
+ tapl.overview.dismissAllTasks()
+
+ device.sleep()
+ wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+
+ device.wakeUpAndGoToHomeScreen()
+
+ // Launch an activity that is shown when the device is locked
+ showWhenLockedApp.launchViaIntent(wmHelper)
+ wmHelper.StateSyncBuilder().withFullScreenApp(showWhenLockedApp).waitForAndVerify()
+
+ device.sleep()
+ wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+ }
+
+ @Test
+ open fun openAppFromLockscreenNotificationWithOverlayApp() {
+ device.wakeUp()
+ NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+ // Wait for the app to launch
+ wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ showWhenLockedApp.exit(wmHelper)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.kt
new file mode 100644
index 0000000..fed077e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromNotificationCold(
+ val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+ val rotation: Rotation = Rotation.ROTATION_0
+) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val device = UiDevice.getInstance(instrumentation)
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+ private val openingNotificationsFromLockScreen = false
+
+ @Before
+ fun setup() {
+ device.wakeUpAndGoToHomeScreen()
+ testApp.launchViaIntent(wmHelper)
+ wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+ testApp.postNotification(wmHelper)
+ device.pressHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+ // Close the app that posted the notification to trigger a cold start next time
+ // it is open - can't just kill it because that would remove the notification.
+ tapl.setExpectedRotationCheckEnabled(false)
+ tapl.goHome()
+ tapl.workspace.switchToOverview()
+ tapl.overview.dismissAllTasks()
+ }
+
+ @Test
+ open fun openAppFromNotificationCold() {
+ NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+ // Wait for the app to launch
+ wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.kt
new file mode 100644
index 0000000..403790e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromNotificationWarm(
+ val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+ val rotation: Rotation = Rotation.ROTATION_0
+) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val device = UiDevice.getInstance(instrumentation)
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+ private val openingNotificationsFromLockScreen = false
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+ @Before
+ fun setup() {
+ device.wakeUpAndGoToHomeScreen()
+ testApp.launchViaIntent(wmHelper)
+ wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+ testApp.postNotification(wmHelper)
+ device.pressHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+
+ @Test
+ open fun openAppFromNotificationWarm() {
+ NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+ // Wait for the app to launch
+ wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.kt
new file mode 100644
index 0000000..d611a42
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsBack
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsBackGesturalNavLandscape :
+ QuickSwitchBetweenTwoAppsBack(Rotation.ROTATION_90) {
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun quickSwitchBetweenTwoAppsBack() = super.quickSwitchBetweenTwoAppsBack()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.kt
new file mode 100644
index 0000000..e6bcbba
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsBack
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsBackGesturalNavPortrait :
+ QuickSwitchBetweenTwoAppsBack(Rotation.ROTATION_0) {
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun quickSwitchBetweenTwoAppsBack() = super.quickSwitchBetweenTwoAppsBack()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.kt
new file mode 100644
index 0000000..2a26d63
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsForward
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape :
+ QuickSwitchBetweenTwoAppsForward(Rotation.ROTATION_90) {
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun quickSwitchBetweenTwoAppsForward() = super.quickSwitchBetweenTwoAppsForward()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.kt
new file mode 100644
index 0000000..5ce7143
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsForward
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait :
+ QuickSwitchBetweenTwoAppsForward(Rotation.ROTATION_0) {
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun quickSwitchBetweenTwoAppsForward() = super.quickSwitchBetweenTwoAppsForward()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.kt
new file mode 100644
index 0000000..cff47bb
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchFromLauncher
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchFromLauncherGesturalNavLandscape : QuickSwitchFromLauncher(Rotation.ROTATION_90) {
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun quickSwitchFromLauncher() = super.quickSwitchFromLauncher()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.kt
new file mode 100644
index 0000000..33095a6
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchFromLauncher
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchFromLauncherGesturalNavPortrait : QuickSwitchFromLauncher(Rotation.ROTATION_0) {
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun quickSwitchFromLauncher() = super.quickSwitchFromLauncher()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt
new file mode 100644
index 0000000..93130c4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.service.quickswitch.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class QuickSwitchBetweenTwoAppsBack(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val testApp1 = SimpleAppHelper(instrumentation)
+ private val testApp2 = NonResizeableAppHelper(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setExpectedRotation(rotation.value)
+ tapl.setIgnoreTaskbarVisibility(true)
+ testApp1.launchViaIntent(wmHelper)
+ testApp2.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun quickSwitchBetweenTwoAppsBack() {
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ wmHelper
+ .StateSyncBuilder()
+ .withFullScreenApp(testApp1)
+ .withNavOrTaskBarVisible()
+ .withStatusBarVisible()
+ .waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ testApp1.exit(wmHelper)
+ testApp2.exit(wmHelper)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt
new file mode 100644
index 0000000..4941eea
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.service.quickswitch.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class QuickSwitchBetweenTwoAppsForward(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val testApp1 = SimpleAppHelper(instrumentation)
+ private val testApp2 = NonResizeableAppHelper(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setExpectedRotation(rotation.value)
+
+ testApp1.launchViaIntent(wmHelper)
+ testApp2.launchViaIntent(wmHelper)
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ wmHelper
+ .StateSyncBuilder()
+ .withFullScreenApp(testApp1)
+ .withNavOrTaskBarVisible()
+ .withStatusBarVisible()
+ .waitForAndVerify()
+ }
+
+ @Test
+ open fun quickSwitchBetweenTwoAppsForward() {
+ tapl.launchedAppState.quickSwitchToPreviousAppSwipeLeft()
+ wmHelper
+ .StateSyncBuilder()
+ .withFullScreenApp(testApp2)
+ .withNavOrTaskBarVisible()
+ .withStatusBarVisible()
+ .waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ testApp1.exit(wmHelper)
+ testApp2.exit(wmHelper)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt
new file mode 100644
index 0000000..7afe526
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.service.quickswitch.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class QuickSwitchFromLauncher(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val testApp = SimpleAppHelper(instrumentation)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ tapl.setExpectedRotationCheckEnabled(false)
+
+ tapl.setExpectedRotation(rotation.value)
+
+ testApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ wmHelper
+ .StateSyncBuilder()
+ .withHomeActivityVisible()
+ .withWindowSurfaceDisappeared(testApp)
+ .waitForAndVerify()
+ }
+
+ @Test
+ open fun quickSwitchFromLauncher() {
+ tapl.workspace.quickSwitchToPreviousApp()
+ wmHelper
+ .StateSyncBuilder()
+ .withFullScreenApp(testApp)
+ .withNavOrTaskBarVisible()
+ .withStatusBarVisible()
+ .waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 6430283..7a2e74b 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -200,6 +200,13 @@
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="false"/>
<activity
+ android:name=".ActivityEmbeddingThirdActivity"
+ android:label="ActivityEmbedding Third"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
+ android:theme="@style/CutoutShortEdges"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="false"/>
+ <activity
android:name=".ActivityEmbeddingAlwaysExpandActivity"
android:label="ActivityEmbedding AlwaysExpand"
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
index 239aba5..6731446 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
@@ -27,4 +27,12 @@
android:layout_height="48dp"
android:text="Finish" />
+ <Button
+ android:id="@+id/launch_third_activity_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_centerHorizontal="true"
+ android:onClick="launchThirdActivity"
+ android:text="Launch a third activity" />
+
</LinearLayout>
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
index 6e78750..dc21027 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.testapp;
import android.app.Activity;
+import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
@@ -40,4 +41,9 @@
}
});
}
+
+ public void launchThirdActivity(View view) {
+ startActivity(new Intent().setComponent(
+ ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT));
+ }
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java
new file mode 100644
index 0000000..3bd7281
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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 android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+
+/**
+ * Activity to be used also as a secondary activity to split with
+ * {@link ActivityEmbeddingMainActivity}.
+ */
+public class ActivityEmbeddingThirdActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_embedding_base_layout);
+ findViewById(R.id.root_activity_layout).setBackgroundColor(Color.RED);
+ }
+}
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 5210618..d84ac42 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
@@ -99,6 +99,12 @@
FLICKER_APP_PACKAGE + ".ActivityEmbeddingSecondaryActivity");
}
+ public static class ThirdActivity {
+ public static final String LABEL = "ActivityEmbeddingThirdActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ActivityEmbeddingThirdActivity");
+ }
+
public static class AlwaysExpandActivity {
public static final String LABEL = "ActivityEmbeddingAlwaysExpandActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/UiBench/Android.bp b/tests/UiBench/Android.bp
index 90e61c5..0d2f2ef 100644
--- a/tests/UiBench/Android.bp
+++ b/tests/UiBench/Android.bp
@@ -24,6 +24,5 @@
"androidx.recyclerview_recyclerview",
"androidx.leanback_leanback",
],
- certificate: "platform",
test_suites: ["device-tests"],
}
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index 47211c5..4fc6ec7 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -18,7 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.android.test.uibench">
- <uses-permission android:name="android.permission.INJECT_EVENTS" />
<application android:allowBackup="false"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
diff --git a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
index 1b2c3c6..06b65a7 100644
--- a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java
@@ -15,15 +15,11 @@
*/
package com.android.test.uibench;
-import android.app.Instrumentation;
+import android.content.Intent;
import android.os.Bundle;
-import android.os.Looper;
-import android.os.MessageQueue;
-import androidx.appcompat.app.AppCompatActivity;
-import android.view.KeyEvent;
import android.widget.EditText;
-import java.util.concurrent.Semaphore;
+import androidx.appcompat.app.AppCompatActivity;
/**
* Note: currently incomplete, complexity of input continuously grows, instead of looping
@@ -32,7 +28,13 @@
* Simulates typing continuously into an EditText.
*/
public class EditTextTypeActivity extends AppCompatActivity {
- Thread mThread;
+
+ /**
+ * Broadcast action: Used to notify UiBenchEditTextTypingMicrobenchmark test when the
+ * test activity was paused.
+ */
+ private static final String ACTION_CANCEL_TYPING_CALLBACK =
+ "com.android.uibench.action.CANCEL_TYPING_CALLBACK";
private static String sSeedText = "";
static {
@@ -46,9 +48,6 @@
sSeedText = builder.toString();
}
- final Object mLock = new Object();
- boolean mShouldStop = false;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -56,55 +55,13 @@
EditText editText = new EditText(this);
editText.setText(sSeedText);
setContentView(editText);
-
- final Instrumentation instrumentation = new Instrumentation();
- final Semaphore sem = new Semaphore(0);
- MessageQueue.IdleHandler handler = new MessageQueue.IdleHandler() {
- @Override
- public boolean queueIdle() {
- // TODO: consider other signaling approaches
- sem.release();
- return true;
- }
- };
- Looper.myQueue().addIdleHandler(handler);
- synchronized (mLock) {
- mShouldStop = false;
- }
- mThread = new Thread(new Runnable() {
- int codes[] = { KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_L,
- KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_SPACE };
- int i = 0;
- @Override
- public void run() {
- while (true) {
- try {
- sem.acquire();
- } catch (InterruptedException e) {
- // TODO, maybe
- }
- int code = codes[i % codes.length];
- if (i % 100 == 99) code = KeyEvent.KEYCODE_ENTER;
-
- synchronized (mLock) {
- if (mShouldStop) break;
- }
-
- // TODO: bit of a race here, since the event can arrive after pause/stop.
- // (Can't synchronize on key send, since it's synchronous.)
- instrumentation.sendKeyDownUpSync(code);
- i++;
- }
- }
- });
- mThread.start();
}
@Override
protected void onPause() {
- synchronized (mLock) {
- mShouldStop = true;
- }
+ // Cancel the typing when the test activity was paused.
+ sendBroadcast(new Intent(ACTION_CANCEL_TYPING_CALLBACK).addFlags(
+ Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY));
super.onPause();
}
}