Merge "Renaming new start user tests"
diff --git a/ApiDocs.bp b/ApiDocs.bp
index a46ecce..90b6603 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -422,26 +422,27 @@
"$(location merge_zips) $(out) $(location :ds-docs-java{.docs.zip}) $(genDir)/ds-docs-kt-moved.zip",
}
-java_genrule {
- name: "ds-docs-switched",
- tools: [
- "switcher4",
- "soong_zip",
- ],
- srcs: [
- ":ds-docs-java{.docs.zip}",
- ":ds-docs-kt{.docs.zip}",
- ],
- out: ["ds-docs-switched.zip"],
- dist: {
- targets: ["docs"],
- },
- cmd: "unzip -q $(location :ds-docs-java{.docs.zip}) -d $(genDir) && " +
- "unzip -q $(location :ds-docs-kt{.docs.zip}) -d $(genDir)/en/reference/kotlin && " +
- "SWITCHER=$$(cd $$(dirname $(location switcher4)) && pwd)/$$(basename $(location switcher4)) && " +
- "(cd $(genDir)/en/reference && $$SWITCHER --work platform) > /dev/null && " +
- "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
-}
+// Disable doc generation until Doclava is migrated to JDK 17 (b/240421555).
+// java_genrule {
+// name: "ds-docs-switched",
+// tools: [
+// "switcher4",
+// "soong_zip",
+// ],
+// srcs: [
+// ":ds-docs-java{.docs.zip}",
+// ":ds-docs-kt{.docs.zip}",
+// ],
+// out: ["ds-docs-switched.zip"],
+// dist: {
+// targets: ["docs"],
+// },
+// cmd: "unzip -q $(location :ds-docs-java{.docs.zip}) -d $(genDir) && " +
+// "unzip -q $(location :ds-docs-kt{.docs.zip}) -d $(genDir)/en/reference/kotlin && " +
+// "SWITCHER=$$(cd $$(dirname $(location switcher4)) && pwd)/$$(basename $(location switcher4)) && " +
+// "(cd $(genDir)/en/reference && $$SWITCHER --work platform) > /dev/null && " +
+// "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
+// }
droiddoc {
name: "ds-static-docs",
diff --git a/core/api/current.txt b/core/api/current.txt
index f2ca78f..d3de158 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -51318,7 +51318,7 @@
method public void onTransactionCommitted();
}
- public static class SurfaceControl.TrustedPresentationThresholds {
+ public static final class SurfaceControl.TrustedPresentationThresholds {
ctor public SurfaceControl.TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0476d79..cf92861 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2121,7 +2121,7 @@
method @Nullable public void query(@NonNull android.app.search.Query, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.search.SearchTarget>>);
method public void registerEmptyQueryResultUpdateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.search.SearchSession.Callback);
method public void requestEmptyQueryResultUpdate();
- method public void unregisterEmptyQueryResultUpdateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.search.SearchSession.Callback);
+ method public void unregisterEmptyQueryResultUpdateCallback(@NonNull android.app.search.SearchSession.Callback);
}
public static interface SearchSession.Callback {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 34afd8a..360113b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3270,6 +3270,7 @@
field public static final String DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_ENABLED = "autofill_credential_manager_enabled";
field public static final String DEVICE_CONFIG_AUTOFILL_CREDENTIAL_MANAGER_IGNORE_VIEWS = "autofill_credential_manager_ignore_views";
field public static final String DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED = "autofill_dialog_enabled";
+ field public static final String DEVICE_CONFIG_AUTOFILL_PCC_CLASSIFICATION_ENABLED = "pcc_classification_enabled";
field public static final String DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES = "smart_suggestion_supported_modes";
field public static final String DEVICE_CONFIG_NON_AUTOFILLABLE_IME_ACTION_IDS = "non_autofillable_ime_action_ids";
field public static final String DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW = "package_deny_list_for_unimportant_view";
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index ae57959..65b0cfb 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -48,6 +48,7 @@
import android.util.SparseArray;
import android.util.TypedValue;
import android.util.Xml;
+import android.view.InputDevice;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -612,9 +613,29 @@
private boolean mIsAccessibilityTool = false;
/**
+ * {@link InputDevice} sources which may send {@link android.view.MotionEvent}s.
+ * @see #setMotionEventSources(int)
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "SOURCE_" }, value = {
+ InputDevice.SOURCE_MOUSE,
+ InputDevice.SOURCE_STYLUS,
+ InputDevice.SOURCE_BLUETOOTH_STYLUS,
+ InputDevice.SOURCE_TRACKBALL,
+ InputDevice.SOURCE_MOUSE_RELATIVE,
+ InputDevice.SOURCE_TOUCHPAD,
+ InputDevice.SOURCE_TOUCH_NAVIGATION,
+ InputDevice.SOURCE_ROTARY_ENCODER,
+ InputDevice.SOURCE_JOYSTICK,
+ InputDevice.SOURCE_SENSOR
+ })
+ public @interface MotionEventSources {}
+
+ /**
* The bit mask of {@link android.view.InputDevice} sources that the accessibility
* service wants to listen to for generic {@link android.view.MotionEvent}s.
*/
+ @MotionEventSources
private int mMotionEventSources = 0;
/**
@@ -966,6 +987,7 @@
* Returns the bit mask of {@link android.view.InputDevice} sources that the accessibility
* service wants to listen to for generic {@link android.view.MotionEvent}s.
*/
+ @MotionEventSources
public int getMotionEventSources() {
return mMotionEventSources;
}
@@ -975,28 +997,29 @@
* service wants to listen to for generic {@link android.view.MotionEvent}s.
*
* <p>
- * Note: including an {@link android.view.InputDevice} source that does not send
+ * Including an {@link android.view.InputDevice} source that does not send
* {@link android.view.MotionEvent}s is effectively a no-op for that source, since you will
* not receive any events from that source.
* </p>
+ *
* <p>
- * Allowed sources include:
- * <li>{@link android.view.InputDevice#SOURCE_MOUSE}</li>
- * <li>{@link android.view.InputDevice#SOURCE_STYLUS}</li>
- * <li>{@link android.view.InputDevice#SOURCE_BLUETOOTH_STYLUS}</li>
- * <li>{@link android.view.InputDevice#SOURCE_TRACKBALL}</li>
- * <li>{@link android.view.InputDevice#SOURCE_MOUSE_RELATIVE}</li>
- * <li>{@link android.view.InputDevice#SOURCE_TOUCHPAD}</li>
- * <li>{@link android.view.InputDevice#SOURCE_TOUCH_NAVIGATION}</li>
- * <li>{@link android.view.InputDevice#SOURCE_ROTARY_ENCODER}</li>
- * <li>{@link android.view.InputDevice#SOURCE_JOYSTICK}</li>
- * <li>{@link android.view.InputDevice#SOURCE_SENSOR}</li>
+ * See {@link android.view.InputDevice} for complete source definitions.
+ * Many input devices send {@link android.view.InputEvent}s from more than one type of source so
+ * you may need to include multiple {@link android.view.MotionEvent} sources here, in addition
+ * to using {@link AccessibilityService#onKeyEvent} to listen to {@link android.view.KeyEvent}s.
+ * </p>
+ *
+ * <p>
+ * <strong>Note:</strong> {@link android.view.InputDevice} sources contain source class bits
+ * that complicate bitwise flag removal operations. To remove a specific source you should
+ * rebuild the entire value using bitwise OR operations on the individual source constants.
* </p>
*
* @param motionEventSources A bit mask of {@link android.view.InputDevice} sources.
* @see AccessibilityService#onMotionEvent
+ * @see MotionEventSources
*/
- public void setMotionEventSources(int motionEventSources) {
+ public void setMotionEventSources(@MotionEventSources int motionEventSources) {
mMotionEventSources = motionEventSources;
}
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f6992c9..d3b03c0 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -774,7 +774,15 @@
return this;
}
- /** @hide */
+ /**
+ * Returns if this broadcast should not run until the process is in an active process state.
+ *
+ * @return {@code true} if this broadcast should not run until the process is in an active
+ * process state. Otherwise, {@code false}.
+ * @see #setDeferUntilActive(boolean)
+ *
+ * @hide
+ */
@SystemApi
public boolean isDeferUntilActive() {
return mIsDeferUntilActive;
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 70d8a5e..bb91ecd 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -2324,10 +2324,13 @@
mUiAutomation.connect(flags);
return mUiAutomation;
}
+ final long startUptime = SystemClock.uptimeMillis();
try {
mUiAutomation.connectWithTimeout(flags, CONNECT_TIMEOUT_MILLIS);
return mUiAutomation;
} catch (TimeoutException e) {
+ final long waited = SystemClock.uptimeMillis() - startUptime;
+ Log.e(TAG, "Unable to connect to UiAutomation. Waited for " + waited + " ms", e);
mUiAutomation.destroy();
mUiAutomation = null;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 1345910..b86b09d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -703,6 +703,16 @@
*/
public static final int FLAG_BUBBLE = 0x00001000;
+ /**
+ * Bit to be bitswised-ored into the {@link #flags} field that should be
+ * set by the system if this notification is not dismissible.
+ *
+ * This flag is for internal use only; applications cannot set this flag directly.
+ * @hide
+ */
+ public static final int FLAG_NO_DISMISS = 0x00002000;
+
+
private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index e323e89..909073e 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -265,15 +265,9 @@
public void onCreate() {
}
- /**
- * Provided as a convenience for agent implementations that need an opportunity
- * to do one-time initialization before the actual backup or restore operation
- * is begun with information about the calling user.
- * <p>
- *
- * @hide
- */
+ /** @hide */
public void onCreate(UserHandle user) {
+ mUser = user;
onCreate();
}
@@ -284,7 +278,6 @@
*/
@Deprecated
public void onCreate(UserHandle user, @BackupDestination int backupDestination) {
- mUser = user;
mBackupDestination = backupDestination;
onCreate(user);
@@ -295,7 +288,6 @@
*/
public void onCreate(UserHandle user, @BackupDestination int backupDestination,
@OperationType int operationType) {
- mUser = user;
mBackupDestination = backupDestination;
mLogger = new BackupRestoreEventLogger(operationType);
diff --git a/core/java/android/app/search/SearchSession.java b/core/java/android/app/search/SearchSession.java
index eda68dd..9e0a1d0 100644
--- a/core/java/android/app/search/SearchSession.java
+++ b/core/java/android/app/search/SearchSession.java
@@ -204,7 +204,6 @@
* @param callback The callback to be unregistered.
*/
public void unregisterEmptyQueryResultUpdateCallback(
- @NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull Callback callback) {
synchronized (mRegisteredCallbacks) {
if (mIsClosed.get()) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a6e074c..6aa7f3f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6237,7 +6237,7 @@
*
* @param permission The name of the permission being checked.
* @param pid The process ID being checked against. Must be > 0.
- * @param uid The user ID being checked against. A uid of 0 is the root
+ * @param uid The UID being checked against. A uid of 0 is the root
* user, which will pass every permission check.
*
* @return {@link PackageManager#PERMISSION_GRANTED} if the given
@@ -6327,7 +6327,7 @@
*
* @param permission The name of the permission being checked.
* @param pid The process ID being checked against. Must be > 0.
- * @param uid The user ID being checked against. A uid of 0 is the root
+ * @param uid The UID being checked against. A uid of 0 is the root
* user, which will pass every permission check.
* @param message A message to include in the exception if it is thrown.
*
@@ -6471,7 +6471,7 @@
*
* @param uri The uri that is being checked.
* @param pid The process ID being checked against. Must be > 0.
- * @param uid The user ID being checked against. A uid of 0 is the root
+ * @param uid The UID being checked against. A uid of 0 is the root
* user, which will pass every permission check.
* @param modeFlags The access modes to check.
*
@@ -6499,7 +6499,7 @@
*
* @param uris The list of URIs that is being checked.
* @param pid The process ID being checked against. Must be > 0.
- * @param uid The user ID being checked against. A uid of 0 is the root
+ * @param uid The UID being checked against. A uid of 0 is the root
* user, which will pass every permission check.
* @param modeFlags The access modes to check for the list of uris
*
@@ -6625,7 +6625,7 @@
* @param writePermission The permission that provides overall write
* access, or null to not do this check.
* @param pid The process ID being checked against. Must be > 0.
- * @param uid The user ID being checked against. A uid of 0 is the root
+ * @param uid The UID being checked against. A uid of 0 is the root
* user, which will pass every permission check.
* @param modeFlags The access modes to check.
*
@@ -6649,7 +6649,7 @@
*
* @param uri The uri that is being checked.
* @param pid The process ID being checked against. Must be > 0.
- * @param uid The user ID being checked against. A uid of 0 is the root
+ * @param uid The UID being checked against. A uid of 0 is the root
* user, which will pass every permission check.
* @param modeFlags The access modes to enforce.
* @param message A message to include in the exception if it is thrown.
@@ -6708,7 +6708,7 @@
* @param writePermission The permission that provides overall write
* access, or null to not do this check.
* @param pid The process ID being checked against. Must be > 0.
- * @param uid The user ID being checked against. A uid of 0 is the root
+ * @param uid The UID being checked against. A uid of 0 is the root
* user, which will pass every permission check.
* @param modeFlags The access modes to enforce.
* @param message A message to include in the exception if it is thrown.
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index fd73719..83f0894 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1103,6 +1103,17 @@
public static final long ALWAYS_SANDBOX_DISPLAY_APIS = 185004937L; // buganizer id
/**
+ * This change id excludes the packages it is applied to from ignoreOrientationRequest behaviour
+ * that can be enabled by the device manufacturers for the com.android.server.wm.DisplayArea
+ * or for the whole display.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_RESPECT_REQUESTED_ORIENTATION = 236283604L; // buganizer id
+
+ /**
* This change id excludes the packages it is applied to from the camera compat force rotation
* treatment. See com.android.server.wm.DisplayRotationCompatPolicy for context.
* @hide
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index a980158..7378ac7 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -2510,8 +2510,18 @@
*
* <p>This returns a mapping of package names for this user id to whether we dispatch Tag
* intents to the package. {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED} or
- * {@link #ACTION_TAG_DISCOVERED} will not be dispatched to an Activity if its package is
- * disallowed.
+ * {@link #ACTION_TAG_DISCOVERED} will not be dispatched to an Activity if its package is
+ * mapped to {@code false}.
+ * <p>There are three different possible cases:
+ * <p>A package not being in the preference list.
+ * It does not contain any Tag intent filters or the user never triggers a Tag detection that
+ * matches the intent filter of the package.
+ * <p>A package being mapped to {@code true}.
+ * When a package has been launched by a tag detection for the first time, the package name is
+ * put to the map and by default mapped to {@code true}. The package will receive Tag intents as
+ * usual.
+ * <p>A package being mapped to {@code false}.
+ * The user chooses to disable this package and it will not receive any Tag intents anymore.
*
* @param userId the user to whom this preference list will belong to
* @return a map of the UserId which indicates the mapping from package name to
diff --git a/core/java/android/service/voice/OWNERS b/core/java/android/service/voice/OWNERS
index 59a0c2e..ec44100 100644
--- a/core/java/android/service/voice/OWNERS
+++ b/core/java/android/service/voice/OWNERS
@@ -1,3 +1,7 @@
# Bug component: 533220
include /core/java/android/app/assist/OWNERS
+
+# The owner here should not be assist owner
+liangyuchen@google.com
+tuanng@google.com
diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java
index 3e7c67e..f20767b 100644
--- a/core/java/android/util/RotationUtils.java
+++ b/core/java/android/util/RotationUtils.java
@@ -27,6 +27,7 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.view.Surface;
import android.view.Surface.Rotation;
import android.view.SurfaceControl;
@@ -245,4 +246,23 @@
throw new IllegalArgumentException("Unknown rotation: " + rotation);
}
}
+
+ /**
+ * Reverses the rotation direction around the Z axis. Note that this method assumes all
+ * rotations are relative to {@link Surface.ROTATION_0}.
+ *
+ * @param rotation the original rotation.
+ * @return the new rotation that should be applied.
+ */
+ @Surface.Rotation
+ public static int reverseRotationDirectionAroundZAxis(@Surface.Rotation int rotation) {
+ // Flipping 270 and 90 has the same effect as changing the direction which rotation is
+ // applied.
+ if (rotation == Surface.ROTATION_90) {
+ rotation = Surface.ROTATION_270;
+ } else if (rotation == Surface.ROTATION_270) {
+ rotation = Surface.ROTATION_90;
+ }
+ return rotation;
+ }
}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index e26c7be..3a02c48 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -337,6 +337,9 @@
@Nullable
public DisplayShape displayShape;
+ @Nullable
+ public SurfaceControl.RefreshRateRange layoutLimitedRefreshRate;
+
public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
@Override
public DisplayInfo createFromParcel(Parcel source) {
@@ -411,7 +414,8 @@
&& brightnessDefault == other.brightnessDefault
&& Objects.equals(roundedCorners, other.roundedCorners)
&& installOrientation == other.installOrientation
- && Objects.equals(displayShape, other.displayShape);
+ && Objects.equals(displayShape, other.displayShape)
+ && Objects.equals(layoutLimitedRefreshRate, other.layoutLimitedRefreshRate);
}
@Override
@@ -466,6 +470,7 @@
roundedCorners = other.roundedCorners;
installOrientation = other.installOrientation;
displayShape = other.displayShape;
+ layoutLimitedRefreshRate = other.layoutLimitedRefreshRate;
}
public void readFromParcel(Parcel source) {
@@ -526,6 +531,7 @@
}
installOrientation = source.readInt();
displayShape = source.readTypedObject(DisplayShape.CREATOR);
+ layoutLimitedRefreshRate = source.readTypedObject(SurfaceControl.RefreshRateRange.CREATOR);
}
@Override
@@ -584,6 +590,7 @@
}
dest.writeInt(installOrientation);
dest.writeTypedObject(displayShape, flags);
+ dest.writeTypedObject(layoutLimitedRefreshRate, flags);
}
@Override
@@ -758,7 +765,7 @@
sb.append(name);
sb.append("\", displayId ");
sb.append(displayId);
- sb.append("\", displayGroupId ");
+ sb.append(", displayGroupId ");
sb.append(displayGroupId);
sb.append(flagsToString(flags));
sb.append(", real ");
@@ -843,6 +850,8 @@
sb.append(brightnessDefault);
sb.append(", installOrientation ");
sb.append(Surface.rotationToString(installOrientation));
+ sb.append(", layoutLimitedRefreshRate ");
+ sb.append(layoutLimitedRefreshRate);
sb.append("}");
return sb.toString();
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b003659..18e7e05 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -1757,7 +1757,7 @@
* Information about the min and max refresh rate DM would like to set the display to.
* @hide
*/
- public static final class RefreshRateRange {
+ public static final class RefreshRateRange implements Parcelable {
public static final String TAG = "RefreshRateRange";
// The tolerance within which we consider something approximately equals.
@@ -1826,6 +1826,35 @@
this.min = other.min;
this.max = other.max;
}
+
+ /**
+ * Writes the RefreshRateRange to parce
+ *
+ * @param dest parcel to write the transaction to
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, @WriteFlags int flags) {
+ dest.writeFloat(min);
+ dest.writeFloat(max);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<RefreshRateRange> CREATOR =
+ new Creator<RefreshRateRange>() {
+ @Override
+ public RefreshRateRange createFromParcel(Parcel in) {
+ return new RefreshRateRange(in.readFloat(), in.readFloat());
+ }
+
+ @Override
+ public RefreshRateRange[] newArray(int size) {
+ return new RefreshRateRange[size];
+ }
+ };
}
/**
@@ -2476,10 +2505,10 @@
* {@link Transaction#setTrustedPresentationCallback(SurfaceControl,
* TrustedPresentationThresholds, Executor, Consumer)}
*/
- public static class TrustedPresentationThresholds {
- private float mMinAlpha;
- private float mMinFractionRendered;
- private int mStabilityRequirementMs;
+ public static final class TrustedPresentationThresholds {
+ private final float mMinAlpha;
+ private final float mMinFractionRendered;
+ private final int mStabilityRequirementMs;
/**
* Creates a TrustedPresentationThresholds that's used when calling
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 872b4f9..cea397c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -8839,6 +8839,8 @@
mInsetsController.dump(prefix, writer);
+ mOnBackInvokedDispatcher.dump(prefix, writer);
+
writer.println(prefix + "View Hierarchy:");
dumpViewHierarchy(innerPrefix, writer, mView);
}
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index 5ec5219..3b8298e 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -17,9 +17,9 @@
package android.view;
import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
@@ -109,14 +109,6 @@
// Ensure that windows with a non-ALWAYS display cutout mode are laid out in
// the cutout safe zone.
final Rect displayFrame = state.getDisplayFrame();
- final InsetsSource statusBarSource = state.peekSource(ITYPE_STATUS_BAR);
- if (statusBarSource != null && displayCutoutSafe.top > displayFrame.top) {
- // Make sure that the zone we're avoiding for the cutout is at least as tall as the
- // status bar; otherwise fullscreen apps will end up cutting halfway into the status
- // bar.
- displayCutoutSafeExceptMaybeBars.top =
- Math.max(statusBarSource.getFrame().bottom, displayCutoutSafe.top);
- }
if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
if (displayFrame.width() < displayFrame.height()) {
displayCutoutSafeExceptMaybeBars.top = MIN_Y;
@@ -131,7 +123,7 @@
&& (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
|| cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
final Insets systemBarsInsets = state.calculateInsets(
- displayFrame, WindowInsets.Type.systemBars(), requestedVisibleTypes);
+ displayFrame, systemBars(), requestedVisibleTypes);
if (systemBarsInsets.left > 0) {
displayCutoutSafeExceptMaybeBars.left = MIN_X;
}
@@ -145,12 +137,11 @@
displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
}
}
- if (type == TYPE_INPUT_METHOD) {
- final InsetsSource navSource = state.peekSource(ITYPE_NAVIGATION_BAR);
- if (navSource != null && navSource.calculateInsets(displayFrame, true).bottom > 0) {
- // The IME can always extend under the bottom cutout if the navbar is there.
- displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
- }
+ if (type == TYPE_INPUT_METHOD
+ && displayCutoutSafeExceptMaybeBars.bottom != MAX_Y
+ && state.calculateInsets(displayFrame, navigationBars(), true).bottom > 0) {
+ // The IME can always extend under the bottom cutout if the navbar is there.
+ displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
}
final boolean attachedInParent = attachedWindowFrame != null && !layoutInScreen;
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index cba399f..e7c610b 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -145,19 +145,13 @@
* <p>For example, a list with only 1 package would be, {@code Package1:;}. A list with one
* denied activity {@code Activity1} under {@code Package1} and a full denied package
* {@code Package2} would be {@code Package1:Activity1;Package2:;}
- *
- * @hide
*/
- @TestApi
public static final String DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW =
"package_deny_list_for_unimportant_view";
/**
* Whether the heuristics check for view is enabled
- *
- * @hide
*/
- @TestApi
public static final String DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW =
"trigger_fill_request_on_unimportant_view";
@@ -169,15 +163,24 @@
*
* <p> For example, a imeAction list could be "2,3,4", corresponding to ime_action definition
* in {@link android.view.inputmethod.EditorInfo.java}</p>
- *
- * @hide
*/
- @TestApi
@SuppressLint("IntentName")
public static final String DEVICE_CONFIG_NON_AUTOFILLABLE_IME_ACTION_IDS =
"non_autofillable_ime_action_ids";
// END AUTOFILL FOR ALL APPS FLAGS //
+
+ // START AUTOFILL PCC CLASSIFICATION FLAGS
+
+ /**
+ * Sets the fill dialog feature enabled or not.
+ */
+ public static final String DEVICE_CONFIG_AUTOFILL_PCC_CLASSIFICATION_ENABLED =
+ "pcc_classification_enabled";
+
+ // END AUTOFILL PCC CLASSIFICATION FLAGS
+
+
/**
* Sets a value of delay time to show up the inline tooltip view.
*
@@ -191,6 +194,7 @@
private static final boolean DEFAULT_HAS_FILL_DIALOG_UI_FEATURE = false;
private static final String DEFAULT_FILL_DIALOG_ENABLED_HINTS = "";
+
// CREDENTIAL MANAGER DEFAULTS
// Credential manager is enabled by default so as to allow testing by app developers
private static final boolean DEFAULT_CREDENTIAL_MANAGER_ENABLED = true;
@@ -199,6 +203,13 @@
private static final boolean DEFAULT_CREDENTIAL_MANAGER_SUPPRESS_SAVE_DIALOG = false;
// END CREDENTIAL MANAGER DEFAULTS
+
+ // AUTOFILL PCC CLASSIFICATION FLAGS DEFAULTS
+ // Default for whether the pcc classification is enabled for autofill.
+ private static final boolean DEFAULT_AUTOFILL_PCC_CLASSIFICATION_ENABLED = false;
+ // END AUTOFILL PCC CLASSIFICATION FLAGS DEFAULTS
+
+
private AutofillFeatureFlags() {};
/**
@@ -302,4 +313,23 @@
DeviceConfig.NAMESPACE_AUTOFILL,
DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW, "");
}
+
+
+ // START AUTOFILL PCC CLASSIFICATION FUNCTIONS
+
+ /**
+ * Whether Autofill PCC Detection is enabled.
+ *
+ * @hide
+ */
+ public static boolean isAutofillPccClassificationEnabled() {
+ // TODO(b/266379948): Add condition for checking whether device has PCC first
+
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_AUTOFILL_PCC_CLASSIFICATION_ENABLED,
+ DEFAULT_AUTOFILL_PCC_CLASSIFICATION_ENABLED);
+ }
+
+ // END AUTOFILL PCC CLASSIFICATION FUNCTIONS
}
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index a0bd7f7..34b75a4 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -211,6 +211,12 @@
IOnBackInvokedCallback getIOnBackInvokedCallback() {
return mIOnBackInvokedCallback;
}
+
+ @Override
+ public String toString() {
+ return "ImeCallback=ImeOnBackInvokedCallback@" + mId
+ + " Callback=" + mIOnBackInvokedCallback;
+ }
}
/**
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index 09d8b0f..56c05b2 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -180,16 +180,7 @@
return;
}
clearCallbacksOnDispatcher();
- if (actualDispatcher instanceof ProxyOnBackInvokedDispatcher) {
- // We don't want to nest ProxyDispatchers, so if we are given on, we unwrap its
- // actual dispatcher.
- // This can happen when an Activity is recreated but the Window is preserved (e.g.
- // when going from split-screen back to single screen)
- mActualDispatcher =
- ((ProxyOnBackInvokedDispatcher) actualDispatcher).mActualDispatcher;
- } else {
- mActualDispatcher = actualDispatcher;
- }
+ mActualDispatcher = actualDispatcher;
transferCallbacksToDispatcher();
}
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 7a5510c..d34ece9 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -29,6 +29,7 @@
import android.view.IWindow;
import android.view.IWindowSession;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
@@ -232,6 +233,26 @@
return Checker.isOnBackInvokedCallbackEnabled(mChecker.getContext());
}
+ /**
+ * Dump information about this WindowOnBackInvokedDispatcher
+ * @param prefix the prefix that will be prepended to each line of the produced output
+ * @param writer the writer that will receive the resulting text
+ */
+ public void dump(String prefix, PrintWriter writer) {
+ String innerPrefix = prefix + " ";
+ writer.println(prefix + "WindowOnBackDispatcher:");
+ if (mAllCallbacks.isEmpty()) {
+ writer.println(prefix + "<None>");
+ return;
+ }
+
+ writer.println(innerPrefix + "Top Callback: " + getTopCallback());
+ writer.println(innerPrefix + "Callbacks: ");
+ mAllCallbacks.forEach((callback, priority) -> {
+ writer.println(innerPrefix + " Callback: " + callback + " Priority=" + priority);
+ });
+ }
+
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
private final WeakReference<OnBackInvokedCallback> mCallback;
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
new file mode 100644
index 0000000..94c230b
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
@@ -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.internal.accessibility.common;
+
+/**
+ * Collection of common constants for accessibility shortcut.
+ */
+public final class MagnificationConstants {
+ private MagnificationConstants() {}
+
+ /**
+ * The min value for the magnification persisted scale. We assume if the scale is lower than
+ * the min value, there will be no obvious magnification effect.
+ */
+ public static final float PERSISTED_SCALE_MIN_VALUE = 1.3f;
+}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index c6aca44..b63041b 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -378,8 +378,12 @@
// window, as we'll be skipping the addView in handleResumeActivity(), and
// the token will not be updated as for a new window.
getAttributes().token = preservedWindow.getAttributes().token;
- mProxyOnBackInvokedDispatcher.setActualDispatcher(
- preservedWindow.getOnBackInvokedDispatcher());
+ final ViewRootImpl viewRoot = mDecor.getViewRootImpl();
+ if (viewRoot != null) {
+ // Clear the old callbacks and attach to the new window.
+ viewRoot.getOnBackInvokedDispatcher().clear();
+ onViewRootImplSet(viewRoot);
+ }
}
// Even though the device doesn't support picture-in-picture mode,
// an user can force using it through developer options.
diff --git a/core/java/com/android/internal/security/VerityUtils.java b/core/java/com/android/internal/security/VerityUtils.java
index 786941f..74a9d16 100644
--- a/core/java/com/android/internal/security/VerityUtils.java
+++ b/core/java/com/android/internal/security/VerityUtils.java
@@ -81,6 +81,15 @@
}
}
+ /** Enables fs-verity for an open file without signature. */
+ public static void setUpFsverity(int fd) throws IOException {
+ int errno = enableFsverityForFdNative(fd);
+ if (errno != 0) {
+ throw new IOException("Failed to enable fs-verity on FD(" + fd + "): "
+ + Os.strerror(errno));
+ }
+ }
+
/** Returns whether the file has fs-verity enabled. */
public static boolean hasFsverity(@NonNull String filePath) {
int retval = statxForFsverityNative(filePath);
@@ -211,6 +220,7 @@
}
private static native int enableFsverityNative(@NonNull String filePath);
+ private static native int enableFsverityForFdNative(int fd);
private static native int measureFsverityNative(@NonNull String filePath,
@NonNull byte[] digest);
private static native int statxForFsverityNative(@NonNull String filePath);
diff --git a/core/jni/com_android_internal_security_VerityUtils.cpp b/core/jni/com_android_internal_security_VerityUtils.cpp
index 3e5689b..4a9e2d4 100644
--- a/core/jni/com_android_internal_security_VerityUtils.cpp
+++ b/core/jni/com_android_internal_security_VerityUtils.cpp
@@ -38,13 +38,8 @@
namespace {
-int enableFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath) {
- ScopedUtfChars path(env, filePath);
- if (path.c_str() == nullptr) {
- return EINVAL;
- }
- ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
- if (rfd.get() < 0) {
+int enableFsverityForFd(JNIEnv *env, jobject clazz, jint fd) {
+ if (fd < 0) {
return errno;
}
@@ -55,12 +50,21 @@
arg.salt_size = 0;
arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr);
- if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) {
+ if (ioctl(fd, FS_IOC_ENABLE_VERITY, &arg) < 0) {
return errno;
}
return 0;
}
+int enableFsverity(JNIEnv *env, jobject clazz, jstring filePath) {
+ ScopedUtfChars path(env, filePath);
+ if (path.c_str() == nullptr) {
+ return EINVAL;
+ }
+ ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
+ return enableFsverityForFd(env, clazz, rfd.get());
+}
+
// Returns whether the file has fs-verity enabled.
// 0 if it is not present, 1 if is present, and -errno if there was an error.
int statxForFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath) {
@@ -126,6 +130,7 @@
}
const JNINativeMethod sMethods[] = {
{"enableFsverityNative", "(Ljava/lang/String;)I", (void *)enableFsverity},
+ {"enableFsverityForFdNative", "(I)I", (void *)enableFsverityForFd},
{"statxForFsverityNative", "(Ljava/lang/String;)I", (void *)statxForFsverity},
{"measureFsverityNative", "(Ljava/lang/String;[B)I", (void *)measureFsverity},
};
diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto
index 4f3eeb0..84c82e0 100644
--- a/core/proto/android/os/system_properties.proto
+++ b/core/proto/android/os/system_properties.proto
@@ -514,7 +514,10 @@
optional string gfx_driver_whitelist_0 = 45;
- // Next Tag: 46
+ optional bool egl_blobcache_multifile = 46;
+ optional int32 egl_blobcache_multifile_limit = 47;
+
+ // Next Tag: 48
}
optional Ro ro = 21;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6aee3cd..0378539 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1751,7 +1751,7 @@
<!-- Allows an application to access wrist temperature data from the watch sensors.
If you're requesting this permission, you must also request
{@link #BODY_SENSORS_WRIST_TEMPERATURE}. Requesting this permission by itself doesn't
- give you heart rate body sensors access.
+ give you wrist temperature body sensors access.
<p class="note"><strong>Note: </strong> This permission is for Wear OS only.
<p>Protection level: dangerous
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0c13484..14eaf34 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -983,6 +983,13 @@
<integer-array name="config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis">
</integer-array>
+ <!-- Boolean indicating whether secondary built-in displays should have their orientation
+ match the active default display. This config assumes that the secondary display only
+ requires swapping ROTATION_90 and ROTATION_270.
+ TODO(b/265991392): This should eventually be configured and parsed in
+ display_settings.xml -->
+ <bool name="config_matchSecondaryInternalDisplaysOrientationToReverseDefaultDisplay">true</bool>
+
<!-- Indicate available ColorDisplayManager.COLOR_MODE_xxx. -->
<integer-array name="config_availableColorModes">
<!-- Example:
@@ -4110,16 +4117,6 @@
-->
<string-array translatable="false" name="config_convert_to_emergency_number_map" />
- <!-- An array of packages for which notifications cannot be blocked.
- Should only be used for core device functionality that must not be
- rendered inoperative for safety reasons, like the phone dialer and
- SMS handler. -->
- <string-array translatable="false" name="config_nonBlockableNotificationPackages">
- <item>com.android.dialer</item>
- <item>com.android.messaging</item>
- <item>com.android.cellbroadcastreceiver.module</item>
- </string-array>
-
<!-- An array of packages that can make sound on the ringer stream in priority-only DND
mode -->
<string-array translatable="false" name="config_priorityOnlyDndExemptPackages">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4260576..7777f11 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3404,6 +3404,13 @@
TODO(b/265312193): Remove this workaround when this bug is fixed.-->
<java-symbol type="array" name="config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis" />
+ <!-- Boolean indicating whether secondary built-in displays should have their orientation
+ match the active default display. This config assumes that the secondary display only
+ requires swapping ROTATION_90 and ROTATION_270.
+ TODO(b/265991392): This should eventually be configured and parsed in
+ display_settings.xml -->
+ <java-symbol type="bool" name="config_matchSecondaryInternalDisplaysOrientationToReverseDefaultDisplay" />
+
<!-- Default user restrictions for the SYSTEM user -->
<java-symbol type="array" name="config_defaultFirstUserRestrictions" />
@@ -3419,7 +3426,6 @@
<java-symbol type="array" name="config_convert_to_emergency_number_map" />
- <java-symbol type="array" name="config_nonBlockableNotificationPackages" />
<java-symbol type="array" name="config_priorityOnlyDndExemptPackages" />
<!-- Screen-size-dependent modes for picker dialogs. -->
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java b/core/tests/coretests/src/android/view/WindowLayoutTests.java
similarity index 89%
rename from services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
rename to core/tests/coretests/src/android/view/WindowLayoutTests.java
index 731a235..5cac98d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
+++ b/core/tests/coretests/src/android/view/WindowLayoutTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 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,11 +14,9 @@
* limitations under the License.
*/
-package com.android.server.wm;
+package android.view;
import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
@@ -39,13 +37,6 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
-import android.view.DisplayCutout;
-import android.view.Gravity;
-import android.view.InsetsSource;
-import android.view.InsetsState;
-import android.view.WindowInsets;
-import android.view.WindowLayout;
-import android.view.WindowManager;
import android.window.ClientWindowFrames;
import androidx.test.filters.SmallTest;
@@ -72,6 +63,11 @@
new Rect(DISPLAY_WIDTH / 4, 0, DISPLAY_WIDTH * 3 / 4, DISPLAY_CUTOUT_HEIGHT);
private static final Insets WATERFALL_INSETS = Insets.of(6, 0, 12, 0);
+ private static final int ID_STATUS_BAR = InsetsSource.createId(
+ null /* owner */, 0 /* index */, statusBars());
+ private static final int ID_NAVIGATION_BAR = InsetsSource.createId(
+ null /* owner */, 0 /* index */, navigationBars());
+
private final WindowLayout mWindowLayout = new WindowLayout();
private final ClientWindowFrames mFrames = new ClientWindowFrames();
@@ -90,9 +86,9 @@
mAttrs = new WindowManager.LayoutParams();
mState = new InsetsState();
mState.setDisplayFrame(new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
- mState.getOrCreateSource(ITYPE_STATUS_BAR, statusBars()).setFrame(
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars()).setFrame(
0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT);
- mState.getOrCreateSource(ITYPE_NAVIGATION_BAR, navigationBars()).setFrame(
+ mState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars()).setFrame(
0, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
mState.getDisplayCutoutSafe(mDisplayCutoutSafe);
mWindowBounds = new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
@@ -268,8 +264,8 @@
@Test
public void fitInvisibleInsets() {
- mState.setSourceVisible(ITYPE_STATUS_BAR, false);
- mState.setSourceVisible(ITYPE_NAVIGATION_BAR, false);
+ mState.setSourceVisible(ID_STATUS_BAR, false);
+ mState.setSourceVisible(ID_NAVIGATION_BAR, false);
computeFrames();
assertInsetByTopBottom(0, 0, mFrames.displayFrame);
@@ -279,8 +275,8 @@
@Test
public void fitInvisibleInsetsIgnoringVisibility() {
- mState.setSourceVisible(ITYPE_STATUS_BAR, false);
- mState.setSourceVisible(ITYPE_NAVIGATION_BAR, false);
+ mState.setSourceVisible(ID_STATUS_BAR, false);
+ mState.setSourceVisible(ID_NAVIGATION_BAR, false);
mAttrs.setFitInsetsIgnoringVisibility(true);
computeFrames();
@@ -324,11 +320,11 @@
mAttrs.setFitInsetsTypes(0);
computeFrames();
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.displayFrame);
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.parentFrame);
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.frame);
}
@@ -365,17 +361,17 @@
@Test
public void layoutInDisplayCutoutModeDefaultWithInvisibleSystemBars() {
addDisplayCutout();
- mState.setSourceVisible(ITYPE_STATUS_BAR, false);
- mState.setSourceVisible(ITYPE_NAVIGATION_BAR, false);
+ mState.setSourceVisible(ID_STATUS_BAR, false);
+ mState.setSourceVisible(ID_NAVIGATION_BAR, false);
mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
mAttrs.setFitInsetsTypes(0);
computeFrames();
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.displayFrame);
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.parentFrame);
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.frame);
}
@@ -398,11 +394,11 @@
mAttrs.setFitInsetsTypes(0);
computeFrames();
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.displayFrame);
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.parentFrame);
- assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
+ assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
mFrames.frame);
}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index bccf283..f2d6250 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1771,6 +1771,12 @@
"group": "WM_DEBUG_KEEP_SCREEN_ON",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-464564167": {
+ "message": "Current transition prevents automatic focus change",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_FOCUS",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-463348344": {
"message": "Removing and adding activity %s to root task at top callers=%s",
"level": "INFO",
@@ -2065,12 +2071,6 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/SurfaceAnimator.java"
},
- "-206549078": {
- "message": "Not moving display (displayId=%d) to top. Top focused displayId=%d. Reason: config_perDisplayFocusEnabled",
- "level": "INFO",
- "group": "WM_DEBUG_FOCUS_LIGHT",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"-203358733": {
"message": "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
"level": "INFO",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 67963a3..d94e8e4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -154,7 +154,7 @@
activityOptions);
// Set adjacent to each other so that the containers below will be invisible.
- setAdjacentTaskFragments(wct, launchingFragmentToken, secondaryFragmentToken, rule);
+ setAdjacentTaskFragmentsWithRule(wct, launchingFragmentToken, secondaryFragmentToken, rule);
setCompanionTaskFragment(wct, launchingFragmentToken, secondaryFragmentToken, rule,
false /* isStacked */);
}
@@ -167,7 +167,7 @@
void expandTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken) {
resizeTaskFragment(wct, fragmentToken, new Rect());
- setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
+ clearAdjacentTaskFragments(wct, fragmentToken);
updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
}
@@ -238,26 +238,37 @@
wct.reparentActivityToTaskFragment(fragmentToken, reparentActivityToken);
}
- void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
- @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) {
- if (secondary == null) {
- wct.clearAdjacentTaskFragments(primary);
- return;
- }
-
+ /**
+ * Sets the two given TaskFragments as adjacent to each other with respecting the given
+ * {@link SplitRule} for {@link WindowContainerTransaction.TaskFragmentAdjacentParams}.
+ */
+ void setAdjacentTaskFragmentsWithRule(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule) {
WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null;
final boolean finishSecondaryWithPrimary =
- splitRule != null && SplitContainer.shouldFinishSecondaryWithPrimary(splitRule);
+ SplitContainer.shouldFinishSecondaryWithPrimary(splitRule);
final boolean finishPrimaryWithSecondary =
- splitRule != null && SplitContainer.shouldFinishPrimaryWithSecondary(splitRule);
+ SplitContainer.shouldFinishPrimaryWithSecondary(splitRule);
if (finishSecondaryWithPrimary || finishPrimaryWithSecondary) {
adjacentParams = new WindowContainerTransaction.TaskFragmentAdjacentParams();
adjacentParams.setShouldDelayPrimaryLastActivityRemoval(finishSecondaryWithPrimary);
adjacentParams.setShouldDelaySecondaryLastActivityRemoval(finishPrimaryWithSecondary);
}
+ setAdjacentTaskFragments(wct, primary, secondary, adjacentParams);
+ }
+
+ void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder primary, @NonNull IBinder secondary,
+ @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams) {
wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
}
+ void clearAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken) {
+ // Clear primary will also clear secondary.
+ wct.clearAdjacentTaskFragments(fragmentToken);
+ }
+
void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule,
boolean isStacked) {
@@ -268,7 +279,7 @@
} else {
finishPrimaryWithSecondary = shouldFinishPrimaryWithSecondary(splitRule);
}
- wct.setCompanionTaskFragment(primary, finishPrimaryWithSecondary ? secondary : null);
+ setCompanionTaskFragment(wct, primary, finishPrimaryWithSecondary ? secondary : null);
final boolean finishSecondaryWithPrimary;
if (isStacked) {
@@ -277,7 +288,12 @@
} else {
finishSecondaryWithPrimary = shouldFinishSecondaryWithPrimary(splitRule);
}
- wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null);
+ setCompanionTaskFragment(wct, secondary, finishSecondaryWithPrimary ? primary : null);
+ }
+
+ void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder primary,
+ @Nullable IBinder secondary) {
+ wct.setCompanionTaskFragment(primary, secondary);
}
void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 825c670..18497ad 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -21,9 +21,11 @@
import android.os.IBinder;
import android.util.Pair;
import android.util.Size;
+import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import androidx.window.extensions.core.util.function.Function;
/**
* Client-side descriptor of a split that holds two containers.
@@ -35,8 +37,12 @@
private final TaskFragmentContainer mSecondaryContainer;
@NonNull
private final SplitRule mSplitRule;
+ /** @see SplitContainer#getCurrentSplitAttributes() */
@NonNull
- private SplitAttributes mSplitAttributes;
+ private SplitAttributes mCurrentSplitAttributes;
+ /** @see SplitContainer#getDefaultSplitAttributes() */
+ @NonNull
+ private SplitAttributes mDefaultSplitAttributes;
@NonNull
private final IBinder mToken;
@@ -48,7 +54,8 @@
mPrimaryContainer = primaryContainer;
mSecondaryContainer = secondaryContainer;
mSplitRule = splitRule;
- mSplitAttributes = splitAttributes;
+ mDefaultSplitAttributes = splitRule.getDefaultSplitAttributes();
+ mCurrentSplitAttributes = splitAttributes;
mToken = new Binder("SplitContainer");
if (shouldFinishPrimaryWithSecondary(splitRule)) {
@@ -82,9 +89,37 @@
return mSplitRule;
}
+ /**
+ * Returns the current {@link SplitAttributes} this {@code SplitContainer} is showing.
+ * <p>
+ * If the {@code SplitAttributes} calculator function is not set by
+ * {@link SplitController#setSplitAttributesCalculator(Function)}, the current
+ * {@code SplitAttributes} is either to expand the containers if the size constraints of
+ * {@link #getSplitRule()} are not satisfied,
+ * or the {@link #getDefaultSplitAttributes()}, otherwise.
+ * </p><p>
+ * If the {@code SplitAttributes} calculator function is set, the current
+ * {@code SplitAttributes} will be customized by the function, which can be any
+ * {@code SplitAttributes}.
+ * </p>
+ *
+ * @see SplitAttributes.SplitType.ExpandContainersSplitType
+ */
@NonNull
- SplitAttributes getSplitAttributes() {
- return mSplitAttributes;
+ SplitAttributes getCurrentSplitAttributes() {
+ return mCurrentSplitAttributes;
+ }
+
+ /**
+ * Returns the default {@link SplitAttributes} when the parent task container bounds satisfy
+ * {@link #getSplitRule()} constraints.
+ * <p>
+ * The value is usually from {@link SplitRule#getDefaultSplitAttributes} unless it is overridden
+ * by {@link SplitController#updateSplitAttributes(IBinder, SplitAttributes)}.
+ */
+ @NonNull
+ SplitAttributes getDefaultSplitAttributes() {
+ return mDefaultSplitAttributes;
}
@NonNull
@@ -95,11 +130,19 @@
/**
* Updates the {@link SplitAttributes} to this container.
* It is usually used when there's a folding state change or
- * {@link SplitController#onTaskFragmentParentInfoChanged(WindowContainerTransaction, int,
- * Configuration)}.
+ * {@link SplitController#onTaskFragmentParentInfoChanged(WindowContainerTransaction,
+ * int, TaskFragmentParentInfo)}.
*/
- void setSplitAttributes(@NonNull SplitAttributes splitAttributes) {
- mSplitAttributes = splitAttributes;
+ void updateCurrentSplitAttributes(@NonNull SplitAttributes splitAttributes) {
+ mCurrentSplitAttributes = splitAttributes;
+ }
+
+ /**
+ * Overrides the default {@link SplitAttributes} to this container, which may be different
+ * from {@link SplitRule#getDefaultSplitAttributes}.
+ */
+ void updateDefaultSplitAttributes(@NonNull SplitAttributes splitAttributes) {
+ mDefaultSplitAttributes = splitAttributes;
}
@NonNull
@@ -121,7 +164,7 @@
@NonNull
SplitInfo toSplitInfo() {
return new SplitInfo(mPrimaryContainer.toActivityStack(),
- mSecondaryContainer.toActivityStack(), mSplitAttributes, mToken);
+ mSecondaryContainer.toActivityStack(), mCurrentSplitAttributes, mToken);
}
static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
@@ -180,9 +223,10 @@
public String toString() {
return "SplitContainer{"
+ " primaryContainer=" + mPrimaryContainer
- + " secondaryContainer=" + mSecondaryContainer
- + " splitRule=" + mSplitRule
- + " splitAttributes" + mSplitAttributes
+ + ", secondaryContainer=" + mSecondaryContainer
+ + ", splitRule=" + mSplitRule
+ + ", currentSplitAttributes" + mCurrentSplitAttributes
+ + ", defaultSplitAttributes" + mDefaultSplitAttributes
+ "}";
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index ff58201..2c1ddf7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -87,6 +87,7 @@
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -279,6 +280,98 @@
}
}
+ @Override
+ public void finishActivityStacks(@NonNull Set<IBinder> activityStackTokens) {
+ if (activityStackTokens.isEmpty()) {
+ return;
+ }
+ synchronized (mLock) {
+ // Translate ActivityStack to TaskFragmentContainer.
+ final List<TaskFragmentContainer> pendingFinishingContainers =
+ activityStackTokens.stream()
+ .map(token -> {
+ synchronized (mLock) {
+ return getContainer(token);
+ }
+ }).filter(Objects::nonNull)
+ .toList();
+
+ if (pendingFinishingContainers.isEmpty()) {
+ return;
+ }
+ // Start transaction with close transit type.
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
+
+ forAllTaskContainers(taskContainer -> {
+ synchronized (mLock) {
+ final List<TaskFragmentContainer> containers = taskContainer.mContainers;
+ // Clean up the TaskFragmentContainers by the z-order from the lowest.
+ for (int i = 0; i < containers.size() - 1; i++) {
+ final TaskFragmentContainer container = containers.get(i);
+ if (pendingFinishingContainers.contains(container)) {
+ // Don't update records here to prevent double invocation.
+ container.finish(false /* shouldFinishDependant */, mPresenter,
+ wct, this, false /* shouldRemoveRecord */);
+ }
+ }
+ // Remove container records.
+ removeContainers(taskContainer, pendingFinishingContainers);
+ // Update the change to the client side.
+ updateContainersInTaskIfVisible(wct, taskContainer.getTaskId());
+ }
+ });
+
+ // Apply the transaction.
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ }
+ }
+
+ @Override
+ public void invalidateTopVisibleSplitAttributes() {
+ synchronized (mLock) {
+ WindowContainerTransaction wct = mTransactionManager.startNewTransaction()
+ .getTransaction();
+ forAllTaskContainers(taskContainer -> {
+ synchronized (mLock) {
+ updateContainersInTaskIfVisible(wct, taskContainer.getTaskId());
+ }
+ });
+ mTransactionManager.getCurrentTransactionRecord()
+ .apply(false /* shouldApplyIndependently */);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void forAllTaskContainers(@NonNull Consumer<TaskContainer> callback) {
+ for (int i = mTaskContainers.size() - 1; i >= 0; --i) {
+ callback.accept(mTaskContainers.valueAt(i));
+ }
+ }
+
+ @Override
+ public void updateSplitAttributes(@NonNull IBinder splitInfoToken,
+ @NonNull SplitAttributes splitAttributes) {
+ synchronized (mLock) {
+ final SplitContainer splitContainer = getSplitContainer(splitInfoToken);
+ if (splitContainer == null) {
+ Log.w(TAG, "Cannot find SplitContainer for token:" + splitInfoToken);
+ return;
+ }
+ WindowContainerTransaction wct = mTransactionManager.startNewTransaction()
+ .getTransaction();
+ if (updateSplitContainerIfNeeded(splitContainer, wct, splitAttributes)) {
+ splitContainer.updateDefaultSplitAttributes(splitAttributes);
+ mTransactionManager.getCurrentTransactionRecord()
+ .apply(false /* shouldApplyIndependently */);
+ } else {
+ // Abort if the SplitContainer wasn't updated.
+ mTransactionManager.getCurrentTransactionRecord().abort();
+ }
+ }
+ }
+
/**
* Called when the transaction is ready so that the organizer can update the TaskFragments based
* on the changes in transaction.
@@ -432,6 +525,9 @@
// All overrides will be cleanup.
container.setLastRequestedBounds(null /* bounds */);
container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED);
+ container.clearLastAdjacentTaskFragment();
+ container.setLastCompanionTaskFragment(null /* fragmentToken */);
+ container.setLastRequestAnimationParams(TaskFragmentAnimationParams.DEFAULT);
cleanupForEnterPip(wct, container);
} else if (wasInPip) {
// Exit PIP.
@@ -648,35 +744,6 @@
}
}
- /** Returns whether the given {@link TaskContainer} may show in split. */
- // Suppress GuardedBy warning because lint asks to mark this method as
- // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
- @SuppressWarnings("GuardedBy")
- @GuardedBy("mLock")
- private boolean mayShowSplit(@NonNull TaskContainer taskContainer) {
- // No split inside PIP.
- if (taskContainer.isInPictureInPicture()) {
- return false;
- }
- // Always assume the TaskContainer if SplitAttributesCalculator is set
- if (mSplitAttributesCalculator != null) {
- return true;
- }
- // Check if the parent container bounds can support any split rule.
- for (EmbeddingRule rule : mSplitRules) {
- if (!(rule instanceof SplitRule)) {
- continue;
- }
- final SplitRule splitRule = (SplitRule) rule;
- final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
- taskContainer.getTaskProperties(), splitRule, null /* minDimensionsPair */);
- if (shouldShowSplit(splitAttributes)) {
- return true;
- }
- }
- return false;
- }
-
@VisibleForTesting
@GuardedBy("mLock")
void onActivityCreated(@NonNull WindowContainerTransaction wct,
@@ -1360,20 +1427,33 @@
* Removes the container from bookkeeping records.
*/
void removeContainer(@NonNull TaskFragmentContainer container) {
+ removeContainers(container.getTaskContainer(), Collections.singletonList(container));
+ }
+
+ /**
+ * Removes containers from bookkeeping records.
+ */
+ void removeContainers(@NonNull TaskContainer taskContainer,
+ @NonNull List<TaskFragmentContainer> containers) {
// Remove all split containers that included this one
- final TaskContainer taskContainer = container.getTaskContainer();
- taskContainer.mContainers.remove(container);
+ taskContainer.mContainers.removeAll(containers);
// Marked as a pending removal which will be removed after it is actually removed on the
// server side (#onTaskFragmentVanished).
// In this way, we can keep track of the Task bounds until we no longer have any
// TaskFragment there.
- taskContainer.mFinishedContainer.add(container.getTaskFragmentToken());
+ taskContainer.mFinishedContainer.addAll(containers.stream().map(
+ TaskFragmentContainer::getTaskFragmentToken).toList());
// Cleanup any split references.
final List<SplitContainer> containersToRemove = new ArrayList<>();
for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
- if (container.equals(splitContainer.getSecondaryContainer())
- || container.equals(splitContainer.getPrimaryContainer())) {
+ if (containersToRemove.contains(splitContainer)) {
+ // Don't need to check because it has been in the remove list.
+ continue;
+ }
+ if (containers.stream().anyMatch(container ->
+ splitContainer.getPrimaryContainer().equals(container)
+ || splitContainer.getSecondaryContainer().equals(container))) {
containersToRemove.add(splitContainer);
}
}
@@ -1381,7 +1461,7 @@
// Cleanup any dependent references.
for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) {
- containerToUpdate.removeContainerToFinishOnExit(container);
+ containerToUpdate.removeContainersToFinishOnExit(containers);
}
}
@@ -1461,26 +1541,53 @@
if (splitContainer == null) {
return;
}
+
+ updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */);
+ }
+
+ /**
+ * Updates {@link SplitContainer} with the given {@link SplitAttributes} if the
+ * {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes}
+ * are {@code null}, the {@link SplitAttributes} will be calculated with
+ * {@link SplitPresenter#computeSplitAttributes(TaskContainer.TaskProperties, SplitRule, Pair)}.
+ *
+ * @param splitContainer The {@link SplitContainer} to update
+ * @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}.
+ * Otherwise, use the value calculated by
+ * {@link SplitPresenter#computeSplitAttributes(
+ * TaskContainer.TaskProperties, SplitRule, Pair)}
+ *
+ * @return {@code true} if the update succeed. Otherwise, returns {@code false}.
+ */
+ @GuardedBy("mLock")
+ private boolean updateSplitContainerIfNeeded(@NonNull SplitContainer splitContainer,
+ @NonNull WindowContainerTransaction wct, @Nullable SplitAttributes splitAttributes) {
if (!isTopMostSplit(splitContainer)) {
// Skip position update - it isn't the topmost split.
- return;
+ return false;
}
if (splitContainer.getPrimaryContainer().isFinished()
|| splitContainer.getSecondaryContainer().isFinished()) {
// Skip position update - one or both containers are finished.
- return;
+ return false;
}
- final TaskContainer taskContainer = splitContainer.getTaskContainer();
- final SplitRule splitRule = splitContainer.getSplitRule();
- final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
- final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
- taskContainer.getTaskProperties(), splitRule, minDimensionsPair);
- splitContainer.setSplitAttributes(splitAttributes);
+ if (splitAttributes == null) {
+ final TaskContainer.TaskProperties taskProperties = splitContainer.getTaskContainer()
+ .getTaskProperties();
+ final SplitRule splitRule = splitContainer.getSplitRule();
+ final SplitAttributes defaultSplitAttributes = splitContainer
+ .getDefaultSplitAttributes();
+ final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
+ splitAttributes = mPresenter.computeSplitAttributes(taskProperties, splitRule,
+ defaultSplitAttributes, minDimensionsPair);
+ }
+ splitContainer.updateCurrentSplitAttributes(splitAttributes);
if (dismissPlaceholderIfNecessary(wct, splitContainer)) {
// Placeholder was finished, the positions will be updated when its container is emptied
- return;
+ return true;
}
- mPresenter.updateSplitContainer(splitContainer, container, wct);
+ mPresenter.updateSplitContainer(splitContainer, wct);
+ return true;
}
/** Whether the given split is the topmost split in the Task. */
@@ -1576,7 +1683,7 @@
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
placeholderRule.getPlaceholderIntent());
final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(taskProperties,
- placeholderRule, minDimensionsPair);
+ placeholderRule, placeholderRule.getDefaultSplitAttributes(), minDimensionsPair);
if (!SplitPresenter.shouldShowSplit(splitAttributes)) {
return false;
}
@@ -1655,7 +1762,7 @@
// The placeholder should remain after it was first shown.
return false;
}
- final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
+ final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
if (SplitPresenter.shouldShowSplit(splitAttributes)) {
return false;
}
@@ -1799,6 +1906,20 @@
@Nullable
@GuardedBy("mLock")
+ SplitContainer getSplitContainer(@NonNull IBinder token) {
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ final List<SplitContainer> containers = mTaskContainers.valueAt(i).mSplitContainers;
+ for (SplitContainer container : containers) {
+ if (container.getToken().equals(token)) {
+ return container;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ @GuardedBy("mLock")
TaskContainer getTaskContainer(int taskId) {
return mTaskContainers.get(taskId);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 2b93682..0408511 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -179,7 +179,7 @@
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
primaryActivity, secondaryIntent);
final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
- minDimensionsPair);
+ rule.getDefaultSplitAttributes(), minDimensionsPair);
final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
@@ -225,7 +225,7 @@
final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
secondaryActivity);
final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
- minDimensionsPair);
+ rule.getDefaultSplitAttributes(), minDimensionsPair);
final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
@@ -334,11 +334,9 @@
/**
* Updates the positions of containers in an existing split.
* @param splitContainer The split container to be updated.
- * @param updatedContainer The task fragment that was updated and caused this split update.
* @param wct WindowContainerTransaction that this update should be performed with.
*/
void updateSplitContainer(@NonNull SplitContainer splitContainer,
- @NonNull TaskFragmentContainer updatedContainer,
@NonNull WindowContainerTransaction wct) {
// Getting the parent configuration using the updated container - it will have the recent
// value.
@@ -348,8 +346,9 @@
if (activity == null) {
return;
}
- final TaskProperties taskProperties = getTaskProperties(updatedContainer);
- final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
+ final TaskContainer taskContainer = splitContainer.getTaskContainer();
+ final TaskProperties taskProperties = taskContainer.getTaskProperties();
+ final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final Rect secondaryRelBounds = getRelBoundsForPosition(POSITION_END, taskProperties,
@@ -370,7 +369,6 @@
// When placeholder is shown in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
}
- final TaskContainer taskContainer = updatedContainer.getTaskContainer();
final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
primaryRelBounds);
updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode);
@@ -387,10 +385,9 @@
// secondaryContainer could not be finished.
boolean isStacked = !shouldShowSplit(splitAttributes);
if (isStacked) {
- setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
- null /* secondary */, null /* splitRule */);
+ clearAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken());
} else {
- setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
+ setAdjacentTaskFragmentsWithRule(wct, primaryContainer.getTaskFragmentToken(),
secondaryContainer.getTaskFragmentToken(), splitRule);
}
setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(),
@@ -427,7 +424,7 @@
fragmentOptions.getFragmentToken());
if (container == null) {
throw new IllegalStateException(
- "Creating a task fragment that is not registered with controller.");
+ "Creating a TaskFragment that is not registered with controller.");
}
container.setLastRequestedBounds(fragmentOptions.getInitialRelativeBounds());
@@ -441,7 +438,7 @@
TaskFragmentContainer container = mController.getContainer(fragmentToken);
if (container == null) {
throw new IllegalStateException(
- "Resizing a task fragment that is not registered with controller.");
+ "Resizing a TaskFragment that is not registered with controller.");
}
if (container.areLastRequestedBoundsEqual(relBounds)) {
@@ -458,7 +455,7 @@
@NonNull IBinder fragmentToken, @WindowingMode int windowingMode) {
final TaskFragmentContainer container = mController.getContainer(fragmentToken);
if (container == null) {
- throw new IllegalStateException("Setting windowing mode for a task fragment that is"
+ throw new IllegalStateException("Setting windowing mode for a TaskFragment that is"
+ " not registered with controller.");
}
@@ -476,7 +473,7 @@
@NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams) {
final TaskFragmentContainer container = mController.getContainer(fragmentToken);
if (container == null) {
- throw new IllegalStateException("Setting animation params for a task fragment that is"
+ throw new IllegalStateException("Setting animation params for a TaskFragment that is"
+ " not registered with controller.");
}
@@ -489,6 +486,64 @@
super.updateAnimationParams(wct, fragmentToken, animationParams);
}
+ @Override
+ void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder primary, @NonNull IBinder secondary,
+ @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams) {
+ final TaskFragmentContainer primaryContainer = mController.getContainer(primary);
+ final TaskFragmentContainer secondaryContainer = mController.getContainer(secondary);
+ if (primaryContainer == null || secondaryContainer == null) {
+ throw new IllegalStateException("setAdjacentTaskFragments on TaskFragment that is"
+ + " not registered with controller.");
+ }
+
+ if (primaryContainer.isLastAdjacentTaskFragmentEqual(secondary, adjacentParams)
+ && secondaryContainer.isLastAdjacentTaskFragmentEqual(primary, adjacentParams)) {
+ // Return early if the same adjacent TaskFragments were already requested
+ return;
+ }
+
+ primaryContainer.setLastAdjacentTaskFragment(secondary, adjacentParams);
+ secondaryContainer.setLastAdjacentTaskFragment(primary, adjacentParams);
+ super.setAdjacentTaskFragments(wct, primary, secondary, adjacentParams);
+ }
+
+ @Override
+ void clearAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken) {
+ final TaskFragmentContainer container = mController.getContainer(fragmentToken);
+ if (container == null) {
+ throw new IllegalStateException("clearAdjacentTaskFragments on TaskFragment that is"
+ + " not registered with controller.");
+ }
+
+ if (container.isLastAdjacentTaskFragmentEqual(null /* fragmentToken*/, null /* params */)) {
+ // Return early if no adjacent TaskFragment was yet requested
+ return;
+ }
+
+ container.clearLastAdjacentTaskFragment();
+ super.clearAdjacentTaskFragments(wct, fragmentToken);
+ }
+
+ @Override
+ void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder primary,
+ @Nullable IBinder secondary) {
+ final TaskFragmentContainer container = mController.getContainer(primary);
+ if (container == null) {
+ throw new IllegalStateException("setCompanionTaskFragment on TaskFragment that is"
+ + " not registered with controller.");
+ }
+
+ if (container.isLastCompanionTaskFragmentEqual(secondary)) {
+ // Return early if the same companion TaskFragment was already requested
+ return;
+ }
+
+ container.setLastCompanionTaskFragment(secondary);
+ super.setCompanionTaskFragment(wct, primary, secondary);
+ }
+
/**
* Expands the split container if the current split bounds are smaller than the Activity or
* Intent that is added to the container.
@@ -515,9 +570,9 @@
// Expand the splitContainer if minimum dimensions are not satisfied.
final TaskContainer taskContainer = splitContainer.getTaskContainer();
final SplitAttributes splitAttributes = sanitizeSplitAttributes(
- taskContainer.getTaskProperties(), splitContainer.getSplitAttributes(),
+ taskContainer.getTaskProperties(), splitContainer.getCurrentSplitAttributes(),
minDimensionsPair);
- splitContainer.setSplitAttributes(splitAttributes);
+ splitContainer.updateCurrentSplitAttributes(splitAttributes);
if (!shouldShowSplit(splitAttributes)) {
// If the client side hasn't received TaskFragmentInfo yet, we can't change TaskFragment
// bounds. Return failure to create a new SplitContainer which fills task bounds.
@@ -540,7 +595,7 @@
}
static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) {
- return shouldShowSplit(splitContainer.getSplitAttributes());
+ return shouldShowSplit(splitContainer.getCurrentSplitAttributes());
}
static boolean shouldShowSplit(@NonNull SplitAttributes splitAttributes) {
@@ -549,12 +604,12 @@
@NonNull
SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties,
- @NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair) {
+ @NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
+ @Nullable Pair<Size, Size> minDimensionsPair) {
final Configuration taskConfiguration = taskProperties.getConfiguration();
final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
mController.getSplitAttributesCalculator();
- final SplitAttributes defaultSplitAttributes = rule.getDefaultSplitAttributes();
final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics);
if (calculator == null) {
if (!areDefaultConstraintsSatisfied) {
@@ -957,11 +1012,6 @@
}
@NonNull
- static TaskProperties getTaskProperties(@NonNull TaskFragmentContainer container) {
- return container.getTaskContainer().getTaskProperties();
- }
-
- @NonNull
TaskProperties getTaskProperties(@NonNull Activity activity) {
final TaskContainer taskContainer = mController.getTaskContainer(
mController.getTaskId(activity));
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index f41295b..4b15bb1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -108,12 +108,6 @@
}
@NonNull
- Configuration getConfiguration() {
- // Make a copy in case the config is updated unexpectedly.
- return new Configuration(mConfiguration);
- }
-
- @NonNull
TaskProperties getTaskProperties() {
return new TaskProperties(mDisplayId, mConfiguration);
}
@@ -157,7 +151,7 @@
@WindowingMode
private int getWindowingMode() {
- return getConfiguration().windowConfiguration.getWindowingMode();
+ return mConfiguration.windowConfiguration.getWindowingMode();
}
/** Whether there is any {@link TaskFragmentContainer} below this Task. */
@@ -220,10 +214,7 @@
}
}
- /**
- * A wrapper class which contains the display ID and {@link Configuration} of a
- * {@link TaskContainer}
- */
+ /** A wrapper class which contains the information of {@link TaskContainer} */
static final class TaskProperties {
private final int mDisplayId;
@NonNull
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 38ac719..b38f824 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -37,8 +37,10 @@
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
/**
* Client-side container for a stack of activities. Corresponds to an instance of TaskFragment
@@ -116,6 +118,27 @@
private TaskFragmentAnimationParams mLastAnimationParams = TaskFragmentAnimationParams.DEFAULT;
/**
+ * TaskFragment token that was requested last via
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}.
+ */
+ @Nullable
+ private IBinder mLastAdjacentTaskFragment;
+
+ /**
+ * {@link WindowContainerTransaction.TaskFragmentAdjacentParams} token that was requested last
+ * via {@link android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS}.
+ */
+ @Nullable
+ private WindowContainerTransaction.TaskFragmentAdjacentParams mLastAdjacentParams;
+
+ /**
+ * TaskFragment token that was requested last via
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT}.
+ */
+ @Nullable
+ private IBinder mLastCompanionTaskFragment;
+
+ /**
* When the TaskFragment has appeared in server, but is empty, we should remove the TaskFragment
* if it is still empty after the timeout.
*/
@@ -436,10 +459,17 @@
* Removes a container that should be finished when this container is finished.
*/
void removeContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToRemove) {
+ removeContainersToFinishOnExit(Collections.singletonList(containerToRemove));
+ }
+
+ /**
+ * Removes container list that should be finished when this container is finished.
+ */
+ void removeContainersToFinishOnExit(@NonNull List<TaskFragmentContainer> containersToRemove) {
if (mIsFinished) {
return;
}
- mContainersToFinishOnExit.remove(containerToRemove);
+ mContainersToFinishOnExit.removeAll(containersToRemove);
}
/**
@@ -478,6 +508,16 @@
@GuardedBy("mController.mLock")
void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
@NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
+ finish(shouldFinishDependent, presenter, wct, controller, true /* shouldRemoveRecord */);
+ }
+
+ /**
+ * Removes all activities that belong to this process and finishes other containers/activities
+ * configured to finish together.
+ */
+ void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
+ @NonNull WindowContainerTransaction wct, @NonNull SplitController controller,
+ boolean shouldRemoveRecord) {
if (!mIsFinished) {
mIsFinished = true;
if (mAppearEmptyTimeout != null) {
@@ -494,8 +534,10 @@
// Cleanup the visuals
presenter.deleteTaskFragment(wct, getTaskFragmentToken());
- // Cleanup the records
- controller.removeContainer(this);
+ if (shouldRemoveRecord) {
+ // Cleanup the records
+ controller.removeContainer(this);
+ }
// Clean up task fragment information
mInfo = null;
}
@@ -571,6 +613,7 @@
/**
* Checks if last requested bounds are equal to the provided value.
* The requested bounds are relative bounds in parent coordinate.
+ * @see WindowContainerTransaction#setRelativeBounds
*/
boolean areLastRequestedBoundsEqual(@Nullable Rect relBounds) {
return (relBounds == null && mLastRequestedBounds.isEmpty())
@@ -580,6 +623,7 @@
/**
* Updates the last requested bounds.
* The requested bounds are relative bounds in parent coordinate.
+ * @see WindowContainerTransaction#setRelativeBounds
*/
void setLastRequestedBounds(@Nullable Rect relBounds) {
if (relBounds == null) {
@@ -589,13 +633,9 @@
}
}
- @NonNull
- Rect getLastRequestedBounds() {
- return mLastRequestedBounds;
- }
-
/**
* Checks if last requested windowing mode is equal to the provided value.
+ * @see WindowContainerTransaction#setWindowingMode
*/
boolean isLastRequestedWindowingModeEqual(@WindowingMode int windowingMode) {
return mLastRequestedWindowingMode == windowingMode;
@@ -603,6 +643,7 @@
/**
* Updates the last requested windowing mode.
+ * @see WindowContainerTransaction#setWindowingMode
*/
void setLastRequestedWindowingMode(@WindowingMode int windowingModes) {
mLastRequestedWindowingMode = windowingModes;
@@ -610,6 +651,7 @@
/**
* Checks if last requested {@link TaskFragmentAnimationParams} are equal to the provided value.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS
*/
boolean areLastRequestedAnimationParamsEqual(
@NonNull TaskFragmentAnimationParams animationParams) {
@@ -618,11 +660,66 @@
/**
* Updates the last requested {@link TaskFragmentAnimationParams}.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ANIMATION_PARAMS
*/
void setLastRequestAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) {
mLastAnimationParams = animationParams;
}
+ /**
+ * Checks if last requested adjacent TaskFragment token and params are equal to the provided
+ * values.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS
+ * @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS
+ */
+ boolean isLastAdjacentTaskFragmentEqual(@Nullable IBinder fragmentToken,
+ @Nullable WindowContainerTransaction.TaskFragmentAdjacentParams params) {
+ return Objects.equals(mLastAdjacentTaskFragment, fragmentToken)
+ && Objects.equals(mLastAdjacentParams, params);
+ }
+
+ /**
+ * Updates the last requested adjacent TaskFragment token and params.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS
+ */
+ void setLastAdjacentTaskFragment(@NonNull IBinder fragmentToken,
+ @NonNull WindowContainerTransaction.TaskFragmentAdjacentParams params) {
+ mLastAdjacentTaskFragment = fragmentToken;
+ mLastAdjacentParams = params;
+ }
+
+ /**
+ * Clears the last requested adjacent TaskFragment token and params.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS
+ */
+ void clearLastAdjacentTaskFragment() {
+ final TaskFragmentContainer lastAdjacentTaskFragment = mLastAdjacentTaskFragment != null
+ ? mController.getContainer(mLastAdjacentTaskFragment)
+ : null;
+ mLastAdjacentTaskFragment = null;
+ mLastAdjacentParams = null;
+ if (lastAdjacentTaskFragment != null) {
+ // Clear the previous adjacent TaskFragment as well.
+ lastAdjacentTaskFragment.clearLastAdjacentTaskFragment();
+ }
+ }
+
+ /**
+ * Checks if last requested companion TaskFragment token is equal to the provided value.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT
+ */
+ boolean isLastCompanionTaskFragmentEqual(@Nullable IBinder fragmentToken) {
+ return Objects.equals(mLastCompanionTaskFragment, fragmentToken);
+ }
+
+ /**
+ * Updates the last requested companion TaskFragment token.
+ * @see android.window.TaskFragmentOperation#OP_TYPE_SET_COMPANION_TASK_FRAGMENT
+ */
+ void setLastCompanionTaskFragment(@Nullable IBinder fragmentToken) {
+ mLastCompanionTaskFragment = fragmentToken;
+ }
+
/** Gets the parent leaf Task id. */
int getTaskId() {
return mTaskContainer.getTaskId();
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index a26311e..17909d4 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -275,7 +275,8 @@
assertNotNull(tf);
assertNotNull(taskContainer);
- assertEquals(TASK_BOUNDS, taskContainer.getConfiguration().windowConfiguration.getBounds());
+ assertEquals(TASK_BOUNDS, taskContainer.getTaskProperties().getConfiguration()
+ .windowConfiguration.getBounds());
}
@Test
@@ -288,7 +289,7 @@
doReturn(true).when(tf).isEmpty();
doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mTransaction,
mActivity, false /* isOnCreated */);
- doNothing().when(mSplitPresenter).updateSplitContainer(any(), any(), any());
+ doNothing().when(mSplitPresenter).updateSplitContainer(any(), any());
mSplitController.updateContainer(mTransaction, tf);
@@ -341,7 +342,7 @@
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitPresenter, never()).updateSplitContainer(any(), any(), any());
+ verify(mSplitPresenter, never()).updateSplitContainer(any(), any());
// Verify if the top active split is updated if both of its containers are not finished.
doReturn(false).when(mSplitController)
@@ -349,7 +350,7 @@
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitPresenter).updateSplitContainer(splitContainer, tf, mTransaction);
+ verify(mSplitPresenter).updateSplitContainer(splitContainer, mTransaction);
}
@Test
@@ -366,14 +367,14 @@
doReturn(false).when(taskContainer).isVisible();
mSplitController.updateContainer(mTransaction, taskFragmentContainer);
- verify(mSplitPresenter, never()).updateSplitContainer(any(), any(), any());
+ verify(mSplitPresenter, never()).updateSplitContainer(any(), any());
// Update the split when the Task is visible.
doReturn(true).when(taskContainer).isVisible();
mSplitController.updateContainer(mTransaction, taskFragmentContainer);
verify(mSplitPresenter).updateSplitContainer(taskContainer.mSplitContainers.get(0),
- taskFragmentContainer, mTransaction);
+ mTransaction);
}
@Test
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index a41e63f..8330156 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -177,6 +177,64 @@
}
@Test
+ public void testSetAdjacentTaskFragments() {
+ final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID);
+ final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID);
+
+ mPresenter.setAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken(), null /* adjacentParams */);
+ verify(mTransaction).setAdjacentTaskFragments(container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken(), null /* adjacentParams */);
+
+ // No request to set the same adjacent TaskFragments.
+ clearInvocations(mTransaction);
+ mPresenter.setAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken(), null /* adjacentParams */);
+
+ verify(mTransaction, never()).setAdjacentTaskFragments(any(), any(), any());
+ }
+
+ @Test
+ public void testClearAdjacentTaskFragments() {
+ final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID);
+ final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID);
+
+ // No request to clear as it is not set by default.
+ mPresenter.clearAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken());
+ verify(mTransaction, never()).clearAdjacentTaskFragments(any());
+
+ mPresenter.setAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken(), null /* adjacentParams */);
+ mPresenter.clearAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken());
+ verify(mTransaction).clearAdjacentTaskFragments(container0.getTaskFragmentToken());
+
+ // No request to clear on either of the previous cleared TasKFragments.
+ clearInvocations(mTransaction);
+ mPresenter.clearAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken());
+ mPresenter.clearAdjacentTaskFragments(mTransaction, container1.getTaskFragmentToken());
+
+ verify(mTransaction, never()).clearAdjacentTaskFragments(any());
+ }
+
+ @Test
+ public void testSetCompanionTaskFragment() {
+ final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID);
+ final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID);
+
+ mPresenter.setCompanionTaskFragment(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken());
+ verify(mTransaction).setCompanionTaskFragment(container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken());
+
+ // No request to set the same adjacent TaskFragments.
+ clearInvocations(mTransaction);
+ mPresenter.setCompanionTaskFragment(mTransaction, container0.getTaskFragmentToken(),
+ container1.getTaskFragmentToken());
+
+ verify(mTransaction, never()).setCompanionTaskFragment(any(), any());
+ }
+
+ @Test
public void testUpdateAnimationParams() {
final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID);
@@ -228,7 +286,7 @@
@Test
public void testGetRelBoundsForPosition_expandContainers() {
- final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType())
.build();
@@ -248,7 +306,7 @@
@Test
public void testGetRelBoundsForPosition_expandContainers_isRelativeToParent() {
- final TaskContainer.TaskProperties taskProperties = getTaskProperty(
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties(
new Rect(100, 100, 500, 1000));
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType())
@@ -273,7 +331,7 @@
false /* splitHorizontally */);
final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
false /* splitHorizontally */);
- final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
.setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
@@ -339,7 +397,7 @@
// Offset TaskBounds to 100, 100. The returned rel bounds shouldn't be affected.
final Rect taskBounds = new Rect(TASK_BOUNDS);
taskBounds.offset(100, 100);
- final TaskContainer.TaskProperties taskProperties = getTaskProperty(taskBounds);
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties(taskBounds);
SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
.setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
@@ -400,7 +458,7 @@
true /* splitHorizontally */);
final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
true /* splitHorizontally */);
- final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
.setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM)
@@ -442,7 +500,7 @@
false /* splitHorizontally */);
final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
false /* splitHorizontally */);
- final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(new SplitAttributes.SplitType.HingeSplitType(
SplitAttributes.SplitType.RatioSplitType.splitEqually()
@@ -506,7 +564,7 @@
@Test
public void testGetRelBoundsForPosition_fallbackToExpandContainers() {
- final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(new SplitAttributes.SplitType.HingeSplitType(
new SplitAttributes.SplitType.ExpandContainersSplitType()
@@ -528,7 +586,7 @@
@Test
public void testGetRelBoundsForPosition_useHingeSplitType() {
- final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(new SplitAttributes.SplitType.HingeSplitType(
new SplitAttributes.SplitType.ExpandContainersSplitType()
@@ -581,13 +639,13 @@
splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
verify(mPresenter, never()).expandTaskFragment(any(), any());
- splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
+ splitContainer.updateCurrentSplitAttributes(SPLIT_ATTRIBUTES);
doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo();
assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded(
mTransaction, splitContainer, mActivity, secondaryActivity,
null /* secondaryIntent */));
- splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
+ splitContainer.updateCurrentSplitAttributes(SPLIT_ATTRIBUTES);
primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity));
secondaryTf.setInfo(mTransaction,
createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
@@ -597,7 +655,7 @@
verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken());
verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken());
- splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
+ splitContainer.updateCurrentSplitAttributes(SPLIT_ATTRIBUTES);
clearInvocations(mPresenter);
assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
@@ -639,37 +697,35 @@
.setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
.setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
.build();
- final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+ final TaskContainer.TaskProperties taskProperties = getTaskProperties();
assertEquals(SPLIT_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
- splitPairRule, null /* minDimensionsPair */));
+ splitPairRule, SPLIT_ATTRIBUTES, null /* minDimensionsPair */));
final Pair<Size, Size> minDimensionsPair = new Pair<>(
new Size(TASK_BOUNDS.width(), TASK_BOUNDS.height()), null);
assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
- splitPairRule, minDimensionsPair));
+ splitPairRule, SPLIT_ATTRIBUTES, minDimensionsPair));
taskProperties.getConfiguration().windowConfiguration.setBounds(new Rect(
TASK_BOUNDS.left + 1, TASK_BOUNDS.top + 1, TASK_BOUNDS.right + 1,
TASK_BOUNDS.bottom + 1));
assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
- splitPairRule, null /* minDimensionsPair */));
+ splitPairRule, SPLIT_ATTRIBUTES, null /* minDimensionsPair */));
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
- .setSplitType(
- new SplitAttributes.SplitType.HingeSplitType(
- SplitAttributes.SplitType.RatioSplitType.splitEqually()
- )
- ).build();
+ .setSplitType(new SplitAttributes.SplitType.HingeSplitType(
+ SplitAttributes.SplitType.RatioSplitType.splitEqually()))
+ .build();
final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
params -> splitAttributes;
mController.setSplitAttributesCalculator(calculator);
assertEquals(splitAttributes, mPresenter.computeSplitAttributes(taskProperties,
- splitPairRule, null /* minDimensionsPair */));
+ splitPairRule, SPLIT_ATTRIBUTES, null /* minDimensionsPair */));
}
@Test
@@ -696,14 +752,15 @@
doReturn(activityConfig).when(mActivityResources).getConfiguration();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(mock(IBinder.class)).when(activity).getActivityToken();
+ doReturn(TASK_ID).when(activity).getTaskId();
return activity;
}
- private static TaskContainer.TaskProperties getTaskProperty() {
- return getTaskProperty(TASK_BOUNDS);
+ private static TaskContainer.TaskProperties getTaskProperties() {
+ return getTaskProperties(TASK_BOUNDS);
}
- private static TaskContainer.TaskProperties getTaskProperty(@NonNull Rect taskBounds) {
+ private static TaskContainer.TaskProperties getTaskProperties(@NonNull Rect taskBounds) {
final Configuration configuration = new Configuration();
configuration.windowConfiguration.setBounds(taskBounds);
return new TaskContainer.TaskProperties(DEFAULT_DISPLAY, configuration);
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 7a6f46c..378ad81 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 0a755f0..cbebae9 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -41,6 +41,7 @@
#include <SkHighContrastFilter.h>
#include <SkImageEncoder.h>
#include <SkImagePriv.h>
+#include <SkJpegGainmapEncoder.h>
#include <SkPixmap.h>
#include <SkRect.h>
#include <SkStream.h>
@@ -458,6 +459,16 @@
}
bool Bitmap::compress(JavaCompressFormat format, int32_t quality, SkWStream* stream) {
+#ifdef __ANDROID__ // TODO: This isn't built for host for some reason?
+ if (hasGainmap() && format == JavaCompressFormat::Jpeg) {
+ SkBitmap baseBitmap = getSkBitmap();
+ SkBitmap gainmapBitmap = gainmap()->bitmap->getSkBitmap();
+ SkJpegEncoder::Options options{.fQuality = quality};
+ return SkJpegGainmapEncoder::EncodeJpegR(stream, baseBitmap.pixmap(), options,
+ gainmapBitmap.pixmap(), options, gainmap()->info);
+ }
+#endif
+
SkBitmap skbitmap;
getSkBitmap(&skbitmap);
return compress(skbitmap, format, quality, stream);
diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java
index b1356f5..cdeef2b 100644
--- a/media/java/android/media/tv/TvRecordingClient.java
+++ b/media/java/android/media/tv/TvRecordingClient.java
@@ -575,7 +575,7 @@
Log.w(TAG, "onError - session not created");
return;
}
- if (mCallback == null) {
+ if (mCallback != null) {
mCallback.onError(error);
}
if (mTvIAppView != null) {
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index 2cb3468..00d42bd 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -31,6 +31,7 @@
"androidx.compose.ui_ui",
"androidx.compose.ui_ui-tooling",
"androidx.core_core-ktx",
+ "androidx.credentials_credentials",
"androidx.lifecycle_lifecycle-extensions",
"androidx.lifecycle_lifecycle-livedata",
"androidx.lifecycle_lifecycle-runtime-ktx",
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 7b53e26..c66ea5e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -16,12 +16,10 @@
package com.android.credentialmanager
-import android.app.PendingIntent
import android.app.slice.Slice
import android.app.slice.SliceSpec
import android.content.Context
import android.content.Intent
-import android.content.pm.SigningInfo
import android.credentials.CreateCredentialRequest
import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
import android.credentials.CredentialOption
@@ -40,18 +38,15 @@
import android.os.Binder
import android.os.Bundle
import android.os.ResultReceiver
-import android.service.credentials.CredentialProviderService
import com.android.credentialmanager.createflow.DisabledProviderInfo
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
-import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toCredentialDataBundle
-import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
-import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
-import com.android.credentialmanager.jetpack.provider.Action
-import com.android.credentialmanager.jetpack.provider.CreateEntry
-import com.android.credentialmanager.jetpack.provider.CredentialCountInformation
-import com.android.credentialmanager.jetpack.provider.CredentialEntry
+import androidx.credentials.CreateCredentialRequest.DisplayInfo
+import androidx.credentials.CreatePublicKeyCredentialRequest
+import androidx.credentials.CreatePasswordRequest
+
+import java.time.Instant
// Consider repo per screen, similar to view model?
class CredentialManagerRepo(
@@ -180,14 +175,14 @@
.Builder("io.enpass.app")
.setSaveEntries(
listOf<Entry>(
- newCreateEntry(
+ CreateTestUtils.newCreateEntry(context,
"key1", "subkey-1", "elisa.beckett@gmail.com",
- 20, 7, 27, 10L,
- "Optional footer description"
+ 20, 7, 27, Instant.ofEpochSecond(10L),
+ "Legal note"
),
- newCreateEntry(
+ CreateTestUtils.newCreateEntry(context,
"key1", "subkey-2", "elisa.work@google.com",
- 20, 7, 27, 12L,
+ 20, 7, 27, Instant.ofEpochSecond(12L),
null
),
)
@@ -200,14 +195,14 @@
.Builder("com.dashlane")
.setSaveEntries(
listOf<Entry>(
- newCreateEntry(
+ CreateTestUtils.newCreateEntry(context,
"key1", "subkey-3", "elisa.beckett@dashlane.com",
- 20, 7, 27, 11L,
+ 20, 7, 27, Instant.ofEpochSecond(11L),
null
),
- newCreateEntry(
+ CreateTestUtils.newCreateEntry(context,
"key1", "subkey-4", "elisa.work@dashlane.com",
- 20, 7, 27, 14L,
+ 20, 7, 27, Instant.ofEpochSecond(14L),
null
),
)
@@ -228,33 +223,33 @@
GetCredentialProviderData.Builder("io.enpass.app")
.setCredentialEntries(
listOf<Entry>(
- newGetEntry(
- "key1", "subkey-1", TYPE_PASSWORD_CREDENTIAL, "Password",
- "elisa.family@outlook.com", null, 3L
+ GetTestUtils.newPasswordEntry(
+ context, "key1", "subkey-1", "elisa.family@outlook.com", null,
+ Instant.ofEpochSecond(8000L)
),
- newGetEntry(
- "key1", "subkey-1", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
- "elisa.bakery@gmail.com", "Elisa Beckett", 0L
+ GetTestUtils.newPasskeyEntry(
+ context, "key1", "subkey-1", "elisa.bakery@gmail.com", "Elisa Beckett",
+ null
),
- newGetEntry(
- "key1", "subkey-2", TYPE_PASSWORD_CREDENTIAL, "Password",
- "elisa.bakery@gmail.com", null, 10L
+ GetTestUtils.newPasswordEntry(
+ context, "key1", "subkey-2", "elisa.bakery@gmail.com", null,
+ Instant.ofEpochSecond(10000L)
),
- newGetEntry(
- "key1", "subkey-3", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
- "elisa.family@outlook.com", "Elisa Beckett", 1L
+ GetTestUtils.newPasskeyEntry(
+ context, "key1", "subkey-3", "elisa.family@outlook.com",
+ "Elisa Beckett", Instant.ofEpochSecond(500L)
),
)
).setAuthenticationEntry(
- newAuthenticationEntry("key2", "subkey-1", TYPE_PASSWORD_CREDENTIAL)
+ GetTestUtils.newAuthenticationEntry(context, "key2", "subkey-1")
).setActionChips(
listOf(
- newActionEntry(
- "key3", "subkey-1", TYPE_PASSWORD_CREDENTIAL,
+ GetTestUtils.newActionEntry(
+ context, "key3", "subkey-1",
"Open Google Password Manager", "elisa.beckett@gmail.com"
),
- newActionEntry(
- "key3", "subkey-2", TYPE_PASSWORD_CREDENTIAL,
+ GetTestUtils.newActionEntry(
+ context, "key3", "subkey-2",
"Open Google Password Manager", "beckett-family@gmail.com"
),
)
@@ -264,137 +259,29 @@
GetCredentialProviderData.Builder("com.dashlane")
.setCredentialEntries(
listOf<Entry>(
- newGetEntry(
- "key1", "subkey-2", TYPE_PASSWORD_CREDENTIAL, "Password",
- "elisa.family@outlook.com", null, 4L
+ GetTestUtils.newPasswordEntry(
+ context, "key1", "subkey-2", "elisa.family@outlook.com", null,
+ Instant.ofEpochSecond(9000L)
),
- newGetEntry(
- "key1", "subkey-3", TYPE_PASSWORD_CREDENTIAL, "Password",
- "elisa.work@outlook.com", null, 11L
+ GetTestUtils.newPasswordEntry(
+ context, "key1", "subkey-3", "elisa.work@outlook.com", null,
+ Instant.ofEpochSecond(11000L)
),
)
).setAuthenticationEntry(
- newAuthenticationEntry("key2", "subkey-1", TYPE_PASSWORD_CREDENTIAL)
+ GetTestUtils.newAuthenticationEntry(context, "key2", "subkey-1")
).setActionChips(
listOf(
- newActionEntry(
- "key3", "subkey-1", TYPE_PASSWORD_CREDENTIAL,
- "Open Enpass"
+ GetTestUtils.newActionEntry(
+ context, "key3", "subkey-1", "Open Enpass",
+ "Manage passwords"
),
)
).build(),
)
}
- private fun newActionEntry(
- key: String,
- subkey: String,
- credentialType: String,
- text: String,
- subtext: String? = null,
- ): Entry {
- val action = Action(text, subtext, null)
- return Entry(
- key,
- subkey,
- Action.toSlice(action)
- )
- }
-
- private fun newAuthenticationEntry(
- key: String,
- subkey: String,
- credentialType: String,
- ): Entry {
- val slice = Slice.Builder(
- Uri.EMPTY, SliceSpec(credentialType, 1)
- )
- return Entry(
- key,
- subkey,
- slice.build()
- )
- }
-
- private fun newGetEntry(
- key: String,
- subkey: String,
- credentialType: String,
- credentialTypeDisplayName: String,
- userName: String,
- userDisplayName: String?,
- lastUsedTimeMillis: Long?,
- ): Entry {
- val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
- .setPackage("com.androidauth.androidvault")
- intent.putExtra("provider_extra_sample", "testprovider")
-
- val pendingIntent = PendingIntent.getActivity(
- context, 1,
- intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
- or PendingIntent.FLAG_ONE_SHOT)
- )
-
- val credentialEntry = CredentialEntry(
- credentialType, credentialTypeDisplayName, userName,
- userDisplayName, pendingIntent, lastUsedTimeMillis
- ?: 0L, null, false
- )
-
- return Entry(
- key,
- subkey,
- CredentialEntry.toSlice(credentialEntry),
- Intent()
- )
- }
-
- private fun newCreateEntry(
- key: String,
- subkey: String,
- providerDisplayName: String,
- passwordCount: Int,
- passkeyCount: Int,
- totalCredentialCount: Int,
- lastUsedTimeMillis: Long,
- footerDescription: String?,
- ): Entry {
- val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
- .setPackage("com.androidauth.androidvault")
- intent.putExtra("provider_extra_sample", "testprovider")
- val pendingIntent = PendingIntent.getActivity(
- context, 1,
- intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
- or PendingIntent.FLAG_ONE_SHOT)
- )
- val createPasswordRequest = android.service.credentials.CreateCredentialRequest(
- android.service.credentials.CallingAppInfo(
- context.applicationInfo.packageName, SigningInfo()
- ),
- TYPE_PASSWORD_CREDENTIAL,
- toCredentialDataBundle("beckett-bakert@gmail.com", "password123")
- )
- val fillInIntent = Intent().putExtra(
- CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
- createPasswordRequest
- )
-
- val createEntry = CreateEntry(
- providerDisplayName, pendingIntent,
- null, lastUsedTimeMillis,
- listOf(
- CredentialCountInformation.createPasswordCountInformation(passwordCount),
- CredentialCountInformation.createPublicKeyCountInformation(passkeyCount),
- ), footerDescription
- )
- return Entry(
- key,
- subkey,
- CreateEntry.toSlice(createEntry),
- fillInIntent
- )
- }
private fun newRemoteEntry(
key: String,
@@ -451,7 +338,7 @@
return RequestInfo.newCreateRequestInfo(
Binder(),
CreateCredentialRequest(
- TYPE_PUBLIC_KEY_CREDENTIAL,
+ "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL",
credentialData,
// TODO: populate with actual data
/*candidateQueryData=*/ Bundle(),
@@ -462,14 +349,13 @@
}
private fun testCreatePasswordRequestInfo(): RequestInfo {
- val data = toCredentialDataBundle("beckett-bakert@gmail.com", "password123")
+ val request = CreatePasswordRequest("beckett-bakert@gmail.com", "password123")
return RequestInfo.newCreateRequestInfo(
Binder(),
CreateCredentialRequest(
TYPE_PASSWORD_CREDENTIAL,
- data,
- // TODO: populate with actual data
- /*candidateQueryData=*/ Bundle(),
+ request.credentialData,
+ request.candidateQueryData,
/*isSystemProviderRequired=*/ false
),
"com.google.android.youtube"
@@ -478,6 +364,10 @@
private fun testCreateOtherCredentialRequestInfo(): RequestInfo {
val data = Bundle()
+ val displayInfo = DisplayInfo("my-username00", "Joe")
+ data.putBundle(
+ "androidx.credentials.BUNDLE_KEY_REQUEST_DISPLAY_INFO",
+ displayInfo.toBundle())
return RequestInfo.newCreateRequestInfo(
Binder(),
CreateCredentialRequest(
@@ -498,7 +388,7 @@
)
.addCredentialOption(
CredentialOption(
- TYPE_PUBLIC_KEY_CREDENTIAL,
+ "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL",
Bundle(),
Bundle(), /*isSystemProviderRequired=*/
false
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 3f705d6..d8420cd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -16,18 +16,22 @@
package com.android.credentialmanager
+import android.app.slice.Slice
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
+import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
import android.credentials.ui.CreateCredentialProviderData
import android.credentials.ui.DisabledProviderData
import android.credentials.ui.Entry
import android.credentials.ui.GetCredentialProviderData
import android.credentials.ui.RequestInfo
import android.graphics.drawable.Drawable
+import android.service.credentials.CredentialEntry
import android.text.TextUtils
import android.util.Log
import com.android.credentialmanager.common.Constants
+import com.android.credentialmanager.common.CredentialType
import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateOptionInfo
@@ -41,17 +45,22 @@
import com.android.credentialmanager.getflow.CredentialEntryInfo
import com.android.credentialmanager.getflow.ProviderInfo
import com.android.credentialmanager.getflow.RemoteEntryInfo
-import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest
-import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
-import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
-import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
-import com.android.credentialmanager.jetpack.provider.Action
-import com.android.credentialmanager.jetpack.provider.AuthenticationAction
-import com.android.credentialmanager.jetpack.provider.CreateEntry
-import com.android.credentialmanager.jetpack.provider.CredentialCountInformation
-import com.android.credentialmanager.jetpack.provider.CredentialEntry
+import androidx.credentials.CreateCredentialRequest
+import androidx.credentials.CreateCustomCredentialRequest
+import androidx.credentials.CreatePasswordRequest
+import androidx.credentials.CreatePublicKeyCredentialRequest
+import androidx.credentials.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
+import androidx.credentials.provider.Action
+import androidx.credentials.provider.AuthenticationAction
+import androidx.credentials.provider.CreateEntry
+import androidx.credentials.provider.CustomCredentialEntry
+import androidx.credentials.provider.PasswordCredentialEntry
+import androidx.credentials.provider.PublicKeyCredentialEntry
+import androidx.credentials.provider.RemoteCreateEntry
+import androidx.credentials.provider.RemoteCredentialEntry
import org.json.JSONObject
+// TODO: remove all !! checks
private fun getAppLabel(
pm: PackageManager,
appPackageName: String
@@ -174,25 +183,76 @@
credentialEntries: List<Entry>,
context: Context,
): List<CredentialEntryInfo> {
- return credentialEntries.map {
- // TODO: handle NPE gracefully
- val credentialEntry = CredentialEntry.fromSlice(it.slice)!!
+ val result: MutableList<CredentialEntryInfo> = mutableListOf()
+ credentialEntries.forEach {
+ val credentialEntry = parseCredentialEntryFromSlice(it.slice)
+ when (credentialEntry) {
+ is PasswordCredentialEntry -> {
+ result.add(CredentialEntryInfo(
+ providerId = providerId,
+ entryKey = it.key,
+ entrySubkey = it.subkey,
+ pendingIntent = credentialEntry.pendingIntent,
+ fillInIntent = it.frameworkExtrasIntent,
+ credentialType = CredentialType.PASSWORD,
+ credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
+ userName = credentialEntry.username.toString(),
+ displayName = credentialEntry.displayName?.toString(),
+ icon = credentialEntry.icon.loadDrawable(context),
+ lastUsedTimeMillis = credentialEntry.lastUsedTime,
+ ))
+ }
+ is PublicKeyCredentialEntry -> {
+ result.add(CredentialEntryInfo(
+ providerId = providerId,
+ entryKey = it.key,
+ entrySubkey = it.subkey,
+ pendingIntent = credentialEntry.pendingIntent,
+ fillInIntent = it.frameworkExtrasIntent,
+ credentialType = CredentialType.PASSKEY,
+ credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
+ userName = credentialEntry.username.toString(),
+ displayName = credentialEntry.displayName?.toString(),
+ icon = credentialEntry.icon.loadDrawable(context),
+ lastUsedTimeMillis = credentialEntry.lastUsedTime,
+ ))
+ }
+ is CustomCredentialEntry -> {
+ result.add(CredentialEntryInfo(
+ providerId = providerId,
+ entryKey = it.key,
+ entrySubkey = it.subkey,
+ pendingIntent = credentialEntry.pendingIntent,
+ fillInIntent = it.frameworkExtrasIntent,
+ credentialType = CredentialType.UNKNOWN,
+ credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
+ userName = credentialEntry.title.toString(),
+ displayName = credentialEntry.subtitle?.toString(),
+ icon = credentialEntry.icon.loadDrawable(context),
+ lastUsedTimeMillis = credentialEntry.lastUsedTime,
+ ))
+ }
+ else -> Log.d(
+ Constants.LOG_TAG,
+ "Encountered unrecognized credential entry ${it.slice.spec?.type}"
+ )
+ }
+ }
+ // TODO: handle empty list due to parsing error.
+ return result
+ }
- // Consider directly move the UI object into the class.
- return@map CredentialEntryInfo(
- providerId = providerId,
- entryKey = it.key,
- entrySubkey = it.subkey,
- pendingIntent = credentialEntry.pendingIntent,
- fillInIntent = it.frameworkExtrasIntent,
- credentialType = credentialEntry.type,
- credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
- userName = credentialEntry.username.toString(),
- displayName = credentialEntry.displayName?.toString(),
- // TODO: proper fallback
- icon = credentialEntry.icon?.loadDrawable(context),
- lastUsedTimeMillis = credentialEntry.lastUsedTimeMillis,
- )
+ private fun parseCredentialEntryFromSlice(slice: Slice): CredentialEntry? {
+ try {
+ when (slice.spec?.type) {
+ TYPE_PASSWORD_CREDENTIAL -> return PasswordCredentialEntry.fromSlice(slice)!!
+ TYPE_PUBLIC_KEY_CREDENTIAL -> return PublicKeyCredentialEntry.fromSlice(slice)!!
+ else -> return CustomCredentialEntry.fromSlice(slice)!!
+ }
+ } catch (e: Exception) {
+ // Try CustomCredentialEntry.fromSlice one last time in case the cause was a failed
+ // password / passkey parsing attempt.
+ return CustomCredentialEntry.fromSlice(slice)
}
}
@@ -205,18 +265,13 @@
if (authEntry == null) {
return null
}
- val authStructuredEntry = AuthenticationAction.fromSlice(
- authEntry!!.slice
- )
- if (authStructuredEntry == null) {
- return null
- }
-
+ val structuredAuthEntry =
+ AuthenticationAction.fromSlice(authEntry.slice) ?: return null
return AuthenticationEntryInfo(
providerId = providerId,
entryKey = authEntry.key,
entrySubkey = authEntry.subkey,
- pendingIntent = authStructuredEntry.pendingIntent,
+ pendingIntent = structuredAuthEntry.pendingIntent,
fillInIntent = authEntry.frameworkExtrasIntent,
title = providerDisplayName,
icon = providerIcon,
@@ -228,11 +283,13 @@
if (remoteEntry == null) {
return null
}
+ val structuredRemoteEntry = RemoteCredentialEntry.fromSlice(remoteEntry.slice)
+ ?: return null
return RemoteEntryInfo(
providerId = providerId,
entryKey = remoteEntry.key,
entrySubkey = remoteEntry.subkey,
- pendingIntent = remoteEntry.pendingIntent,
+ pendingIntent = structuredRemoteEntry.pendingIntent,
fillInIntent = remoteEntry.frameworkExtrasIntent,
)
}
@@ -242,22 +299,22 @@
actionEntries: List<Entry>,
providerIcon: Drawable,
): List<ActionEntryInfo> {
- return actionEntries.map {
- // TODO: handle NPE gracefully
- val actionEntryUi = Action.fromSlice(it.slice)!!
-
- return@map ActionEntryInfo(
+ val result: MutableList<ActionEntryInfo> = mutableListOf()
+ actionEntries.forEach {
+ val actionEntryUi = Action.fromSlice(it.slice) ?: return@forEach
+ result.add(ActionEntryInfo(
providerId = providerId,
entryKey = it.key,
entrySubkey = it.subkey,
pendingIntent = actionEntryUi.pendingIntent,
fillInIntent = it.frameworkExtrasIntent,
title = actionEntryUi.title.toString(),
- // TODO: gracefully fail
icon = providerIcon,
- subTitle = actionEntryUi.subTitle?.toString(),
- )
+ subTitle = actionEntryUi.subtitle?.toString(),
+ ))
}
+ // TODO: handle empty list
+ return result
}
}
}
@@ -316,22 +373,21 @@
): RequestDisplayInfo? {
val appLabel = getAppLabel(context.packageManager, requestInfo.appPackageName)
?: return null
- val createCredentialRequest = requestInfo.createCredentialRequest
- val createCredentialRequestJetpack = createCredentialRequest?.let {
- CreateCredentialRequest.createFrom(
- it.type, it.credentialData, it.candidateQueryData, it.isSystemProviderRequired
+ val createCredentialRequest = requestInfo.createCredentialRequest ?: return null
+ val createCredentialRequestJetpack = CreateCredentialRequest.createFrom(
+ createCredentialRequest.type,
+ createCredentialRequest.credentialData,
+ createCredentialRequest.candidateQueryData,
+ createCredentialRequest.isSystemProviderRequired
+ )
+ return when (createCredentialRequestJetpack) {
+ is CreatePasswordRequest -> RequestDisplayInfo(
+ createCredentialRequestJetpack.id,
+ createCredentialRequestJetpack.password,
+ CredentialType.PASSWORD,
+ appLabel,
+ context.getDrawable(R.drawable.ic_password)!!
)
- }
- when (createCredentialRequestJetpack) {
- is CreatePasswordRequest -> {
- return RequestDisplayInfo(
- createCredentialRequestJetpack.id,
- createCredentialRequestJetpack.password,
- createCredentialRequestJetpack.type,
- appLabel,
- context.getDrawable(R.drawable.ic_password)!!
- )
- }
is CreatePublicKeyCredentialRequest -> {
val requestJson = createCredentialRequestJetpack.requestJson
val json = JSONObject(requestJson)
@@ -342,24 +398,29 @@
name = user.getString("name")
displayName = user.getString("displayName")
}
- return RequestDisplayInfo(
+ RequestDisplayInfo(
name,
displayName,
- createCredentialRequestJetpack.type,
+ CredentialType.PASSKEY,
appLabel,
context.getDrawable(R.drawable.ic_passkey)!!
)
}
- // TODO: correctly parsing for other sign-ins
- else -> {
- return RequestDisplayInfo(
- "beckett-bakert@gmail.com",
- "Elisa Beckett",
- "other-sign-ins",
- appLabel.toString(),
- context.getDrawable(R.drawable.ic_other_sign_in)!!
+ is CreateCustomCredentialRequest -> {
+ // TODO: directly use the display info once made public
+ val displayInfo = CreateCredentialRequest.DisplayInfo
+ .parseFromCredentialDataBundle(createCredentialRequest.credentialData)
+ ?: return null
+ RequestDisplayInfo(
+ title = displayInfo.userId,
+ subtitle = displayInfo.userDisplayName,
+ type = CredentialType.UNKNOWN,
+ appName = appLabel,
+ typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context)
+ ?: context.getDrawable(R.drawable.ic_other_sign_in)!!
)
}
+ else -> null
}
}
@@ -408,7 +469,7 @@
currentScreenState = initialScreenState,
requestDisplayInfo = requestDisplayInfo,
sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
- compareByDescending { it.first.lastUsedTimeMillis }
+ compareByDescending { it.first.lastUsedTime }
),
hasDefaultProvider = defaultProvider != null,
activeEntry = toActiveEntry(
@@ -430,7 +491,7 @@
isPasskeyFirstUse: Boolean,
): CreateScreenState? {
return if (isPasskeyFirstUse && requestDisplayInfo.type ==
- TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) {
+ CredentialType.PASSKEY && !isOnPasskeyIntroStateAlready) {
CreateScreenState.PASSKEY_INTRO
} else if ((defaultProvider == null || defaultProvider.createOptions.isEmpty()) &&
createOptionSize > 1) {
@@ -479,12 +540,10 @@
creationEntries: List<Entry>,
context: Context,
): List<CreateOptionInfo> {
- return creationEntries.map {
- // TODO: handle NPE gracefully
- val createEntry = CreateEntry.fromSlice(it.slice)!!
-
- return@map CreateOptionInfo(
- // TODO: remove fallbacks
+ val result: MutableList<CreateOptionInfo> = mutableListOf()
+ creationEntries.forEach {
+ val createEntry = CreateEntry.fromSlice(it.slice) ?: return@forEach
+ result.add(CreateOptionInfo(
providerId = providerId,
entryKey = it.key,
entrySubkey = it.subkey,
@@ -492,32 +551,28 @@
fillInIntent = it.frameworkExtrasIntent,
userProviderDisplayName = createEntry.accountName.toString(),
profileIcon = createEntry.icon?.loadDrawable(context),
- passwordCount = CredentialCountInformation.getPasswordCount(
- createEntry.credentialCountInformationList
- ) ?: 0,
- passkeyCount = CredentialCountInformation.getPasskeyCount(
- createEntry.credentialCountInformationList
- ) ?: 0,
- totalCredentialCount = CredentialCountInformation.getTotalCount(
- createEntry.credentialCountInformationList
- ) ?: 0,
- lastUsedTimeMillis = createEntry.lastUsedTimeMillis ?: 0,
- footerDescription = createEntry.footerDescription?.toString()
- )
+ passwordCount = createEntry.getPasswordCredentialCount(),
+ passkeyCount = createEntry.getPublicKeyCredentialCount(),
+ totalCredentialCount = createEntry.getTotalCredentialCount(),
+ lastUsedTime = createEntry.lastUsedTime,
+ footerDescription = createEntry.description?.toString()
+ ))
}
+ return result
}
private fun toRemoteInfo(
providerId: String,
remoteEntry: Entry?,
): RemoteInfo? {
- // TODO: should also call fromSlice after getting the official jetpack code.
return if (remoteEntry != null) {
+ val structuredRemoteEntry = RemoteCreateEntry.fromSlice(remoteEntry.slice)
+ ?: return null
RemoteInfo(
providerId = providerId,
entryKey = remoteEntry.key,
entrySubkey = remoteEntry.subkey,
- pendingIntent = remoteEntry.pendingIntent,
+ pendingIntent = structuredRemoteEntry.pendingIntent,
fillInIntent = remoteEntry.frameworkExtrasIntent,
)
} else null
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
new file mode 100644
index 0000000..e3bbaeb
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
@@ -0,0 +1,352 @@
+/*
+ * 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.credentialmanager
+
+import android.app.PendingIntent
+import android.app.slice.Slice
+import android.app.slice.SliceSpec
+import android.content.Context
+import android.content.Intent
+import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
+import android.credentials.ui.Entry
+import android.graphics.drawable.Icon
+import android.net.Uri
+import android.provider.Settings
+import androidx.credentials.provider.CreateEntry
+
+import java.time.Instant
+
+// TODO: remove once testing is complete
+class GetTestUtils {
+ companion object {
+ internal fun newAuthenticationEntry(
+ context: Context,
+ key: String,
+ subkey: String,
+ ): Entry {
+ val slice = Slice.Builder(
+ Uri.EMPTY, SliceSpec("AuthenticationAction", 0)
+ )
+ val intent = Intent(Settings.ACTION_SYNC_SETTINGS)
+ val pendingIntent =
+ PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+ slice.addAction(
+ pendingIntent,
+ Slice.Builder(slice)
+ .addHints(listOf("androidx.credentials.provider.authenticationAction" +
+ ".SLICE_HINT_PENDING_INTENT"))
+ .build(),
+ /*subType=*/null
+ )
+ return Entry(
+ key,
+ subkey,
+ slice.build()
+ )
+ }
+
+ internal fun newActionEntry(
+ context: Context,
+ key: String,
+ subkey: String,
+ text: String,
+ subtext: String? = null,
+ ): Entry {
+ val intent = Intent(Settings.ACTION_SYNC_SETTINGS)
+ val pendingIntent =
+ PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+ val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec("Action", 0))
+ .addText(
+ text, /*subType=*/null,
+ listOf("androidx.credentials.provider.action.HINT_ACTION_TITLE")
+ )
+ .addText(
+ subtext, /*subType=*/null,
+ listOf("androidx.credentials.provider.action.HINT_ACTION_SUBTEXT")
+ )
+ sliceBuilder.addAction(
+ pendingIntent,
+ Slice.Builder(sliceBuilder)
+ .addHints(
+ listOf("androidx.credentials.provider.action.SLICE_HINT_PENDING_INTENT")
+ )
+ .build(),
+ /*subType=*/null
+ )
+ return Entry(
+ key,
+ subkey,
+ sliceBuilder.build()
+ )
+ }
+
+ private const val SLICE_HINT_TYPE_DISPLAY_NAME =
+ "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
+ private const val SLICE_HINT_TITLE =
+ "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_USER_NAME"
+ private const val SLICE_HINT_SUBTITLE =
+ "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
+ private const val SLICE_HINT_LAST_USED_TIME_MILLIS =
+ "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
+ private const val SLICE_HINT_ICON =
+ "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_PROFILE_ICON"
+ private const val SLICE_HINT_PENDING_INTENT =
+ "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_PENDING_INTENT"
+ private const val SLICE_HINT_AUTO_ALLOWED =
+ "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_AUTO_ALLOWED"
+ private const val AUTO_SELECT_TRUE_STRING = "true"
+ private const val AUTO_SELECT_FALSE_STRING = "false"
+
+ internal fun newPasswordEntry(
+ context: Context,
+ key: String,
+ subkey: String,
+ userName: String,
+ userDisplayName: String?,
+ lastUsedTime: Instant?,
+ ): Entry {
+ val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
+ .setPackage("com.androidauth.androidvault")
+ intent.putExtra("provider_extra_sample", "testprovider")
+ val pendingIntent = PendingIntent.getActivity(
+ context, 1,
+ intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+ or PendingIntent.FLAG_ONE_SHOT)
+ )
+ return Entry(
+ key,
+ subkey,
+ toPasswordSlice(userName, userDisplayName, pendingIntent, lastUsedTime),
+ Intent()
+ )
+ }
+
+ private fun toPasswordSlice(
+ title: CharSequence,
+ subTitle: CharSequence?,
+ pendingIntent: PendingIntent,
+ lastUsedTime: Instant?,
+ icon: Icon? = null,
+ isAutoSelectAllowed: Boolean = true
+ ): Slice {
+ val type = TYPE_PASSWORD_CREDENTIAL
+ val autoSelectAllowed = if (isAutoSelectAllowed) {
+ AUTO_SELECT_TRUE_STRING
+ } else {
+ AUTO_SELECT_FALSE_STRING
+ }
+ val sliceBuilder = Slice.Builder(
+ Uri.EMPTY, SliceSpec(
+ type, 1
+ )
+ )
+ .addText(
+ "Password", /*subType=*/null,
+ listOf(SLICE_HINT_TYPE_DISPLAY_NAME)
+ )
+ .addText(
+ title, /*subType=*/null,
+ listOf(SLICE_HINT_TITLE)
+ )
+ .addText(
+ subTitle, /*subType=*/null,
+ listOf(SLICE_HINT_SUBTITLE)
+ )
+ .addText(
+ autoSelectAllowed, /*subType=*/null,
+ listOf(SLICE_HINT_AUTO_ALLOWED)
+ )
+ if (lastUsedTime != null) {
+ sliceBuilder.addLong(
+ lastUsedTime.toEpochMilli(),
+ /*subType=*/null,
+ listOf(SLICE_HINT_LAST_USED_TIME_MILLIS)
+ )
+ }
+ if (icon != null) {
+ sliceBuilder.addIcon(
+ icon, /*subType=*/null,
+ listOf(SLICE_HINT_ICON)
+ )
+ }
+ sliceBuilder.addAction(
+ pendingIntent,
+ Slice.Builder(sliceBuilder)
+ .addHints(listOf(SLICE_HINT_PENDING_INTENT))
+ .build(),
+ /*subType=*/null
+ )
+ return sliceBuilder.build()
+ }
+
+
+ private const val PASSKEY_SLICE_HINT_TYPE_DISPLAY_NAME =
+ "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
+ private const val PASSKEY_SLICE_HINT_TITLE =
+ "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_USER_NAME"
+ private const val PASSKEY_SLICE_HINT_SUBTITLE =
+ "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
+ private const val PASSKEY_SLICE_HINT_LAST_USED_TIME_MILLIS =
+ "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
+ private const val PASSKEY_SLICE_HINT_ICON =
+ "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_PROFILE_ICON"
+ private const val PASSKEY_SLICE_HINT_PENDING_INTENT =
+ "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_PENDING_INTENT"
+ private const val PASSKEY_SLICE_HINT_AUTO_ALLOWED =
+ "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_AUTO_ALLOWED"
+
+ internal fun newPasskeyEntry(
+ context: Context,
+ key: String,
+ subkey: String,
+ userName: String,
+ userDisplayName: String?,
+ lastUsedTime: Instant?,
+ ): Entry {
+ val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
+ .setPackage("com.androidauth.androidvault")
+ intent.putExtra("provider_extra_sample", "testprovider")
+ val pendingIntent = PendingIntent.getActivity(
+ context, 1,
+ intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+ or PendingIntent.FLAG_ONE_SHOT)
+ )
+ return Entry(
+ key, subkey, toPasskeySlice(
+ userName, userDisplayName, pendingIntent, lastUsedTime
+ ),
+ Intent()
+ )
+ }
+
+ private fun toPasskeySlice(
+ title: CharSequence,
+ subTitle: CharSequence?,
+ pendingIntent: PendingIntent,
+ lastUsedTime: Instant?,
+ icon: Icon? = null,
+ isAutoSelectAllowed: Boolean = true
+ ): Slice {
+ val type = "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"
+ val autoSelectAllowed = if (isAutoSelectAllowed) {
+ AUTO_SELECT_TRUE_STRING
+ } else {
+ AUTO_SELECT_FALSE_STRING
+ }
+ val sliceBuilder = Slice.Builder(
+ Uri.EMPTY, SliceSpec(
+ type, 1
+ )
+ )
+ .addText(
+ "Passkey", /*subType=*/null,
+ listOf(PASSKEY_SLICE_HINT_TYPE_DISPLAY_NAME)
+ )
+ .addText(
+ title, /*subType=*/null,
+ listOf(PASSKEY_SLICE_HINT_TITLE)
+ )
+ .addText(
+ subTitle, /*subType=*/null,
+ listOf(PASSKEY_SLICE_HINT_SUBTITLE)
+ )
+ .addText(
+ autoSelectAllowed, /*subType=*/null,
+ listOf(PASSKEY_SLICE_HINT_AUTO_ALLOWED)
+ )
+ if (lastUsedTime != null) {
+ sliceBuilder.addLong(
+ lastUsedTime.toEpochMilli(),
+ /*subType=*/null,
+ listOf(PASSKEY_SLICE_HINT_LAST_USED_TIME_MILLIS)
+ )
+ }
+ if (icon != null) {
+ sliceBuilder.addIcon(
+ icon, /*subType=*/null,
+ listOf(PASSKEY_SLICE_HINT_ICON)
+ )
+ }
+ sliceBuilder.addAction(
+ pendingIntent,
+ Slice.Builder(sliceBuilder)
+ .addHints(listOf(PASSKEY_SLICE_HINT_PENDING_INTENT))
+ .build(),
+ /*subType=*/null
+ )
+ return sliceBuilder.build()
+ }
+ }
+}
+
+class CreateTestUtils {
+ companion object {
+ private const val TYPE_TOTAL_CREDENTIAL = "TOTAL_CREDENTIAL_COUNT_TYPE"
+ private const val SLICE_HINT_ACCOUNT_NAME =
+ "androidx.credentials.provider.createEntry.SLICE_HINT_USER_PROVIDER_ACCOUNT_NAME"
+ private const val SLICE_HINT_NOTE =
+ "androidx.credentials.provider.createEntry.SLICE_HINT_NOTE"
+ private const val SLICE_HINT_ICON =
+ "androidx.credentials.provider.createEntry.SLICE_HINT_PROFILE_ICON"
+ private const val SLICE_HINT_CREDENTIAL_COUNT_INFORMATION =
+ "androidx.credentials.provider.createEntry.SLICE_HINT_CREDENTIAL_COUNT_INFORMATION"
+ private const val SLICE_HINT_LAST_USED_TIME_MILLIS =
+ "androidx.credentials.provider.createEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
+ private const val SLICE_HINT_PENDING_INTENT =
+ "androidx.credentials.provider.createEntry.SLICE_HINT_PENDING_INTENT"
+
+ internal fun newCreateEntry(
+ context: Context,
+ key: String,
+ subkey: String,
+ providerUserDisplayName: String,
+ passwordCount: Int?,
+ passkeyCount: Int?,
+ totalCredentialCount: Int?,
+ lastUsedTime: Instant?,
+ footerDescription: String?,
+ ): Entry {
+ val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
+ .setPackage("com.androidauth.androidvault")
+ intent.putExtra("provider_extra_sample", "testprovider")
+ val pendingIntent = PendingIntent.getActivity(
+ context, 1,
+ intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+ or PendingIntent.FLAG_ONE_SHOT)
+ )
+ val credCountMap = mutableMapOf<String, Int>()
+ passwordCount?.let { credCountMap.put(TYPE_PASSWORD_CREDENTIAL, it) }
+ passkeyCount?.let {
+ credCountMap.put("androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL", it)
+ }
+ totalCredentialCount?.let { credCountMap.put(TYPE_TOTAL_CREDENTIAL, it) }
+ return Entry(
+ key,
+ subkey,
+ CreateEntry.toSlice(
+ providerUserDisplayName,
+ null,
+ footerDescription,
+ lastUsedTime,
+ credCountMap,
+ pendingIntent
+ ),
+ Intent()
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/CredentialType.kt
similarity index 64%
rename from packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt
rename to packages/CredentialManager/src/com/android/credentialmanager/common/CredentialType.kt
index 497c272..cc92f60 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/CredentialType.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,12 +14,8 @@
* limitations under the License.
*/
-package com.android.credentialmanager.jetpack.developer
+package com.android.credentialmanager.common
-/**
- * Internal exception used to indicate a parsing error while converting from a framework type to
- * a jetpack type.
- *
- * @hide
- */
-internal class FrameworkClassParsingException : Exception()
\ No newline at end of file
+enum class CredentialType {
+ UNKNOWN, PASSKEY, PASSWORD,
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 5e432b9..f8d008e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -2,7 +2,6 @@
package com.android.credentialmanager.createflow
-import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
@@ -37,6 +36,7 @@
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.R
+import com.android.credentialmanager.common.CredentialType
import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
@@ -50,7 +50,6 @@
import com.android.credentialmanager.common.ui.ContainerCard
import com.android.credentialmanager.ui.theme.EntryShape
import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
-import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -288,11 +287,11 @@
text = stringResource(
R.string.choose_provider_title,
when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL ->
+ CredentialType.PASSKEY ->
stringResource(R.string.passkeys)
- TYPE_PASSWORD_CREDENTIAL ->
+ CredentialType.PASSWORD ->
stringResource(R.string.passwords)
- else -> stringResource(R.string.sign_in_info)
+ CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
}),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(horizontal = 24.dp)
@@ -398,11 +397,11 @@
stringResource(
R.string.save_credential_to_title,
when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL ->
+ CredentialType.PASSKEY ->
stringResource(R.string.passkey)
- TYPE_PASSWORD_CREDENTIAL ->
+ CredentialType.PASSWORD ->
stringResource(R.string.password)
- else -> stringResource(R.string.sign_in_info)
+ CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
}),
style = MaterialTheme.typography.titleMedium,
)
@@ -571,15 +570,15 @@
)
TextOnSurface(
text = when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(
+ CredentialType.PASSKEY -> stringResource(
R.string.choose_create_option_passkey_title,
requestDisplayInfo.appName
)
- TYPE_PASSWORD_CREDENTIAL -> stringResource(
+ CredentialType.PASSWORD -> stringResource(
R.string.choose_create_option_password_title,
requestDisplayInfo.appName
)
- else -> stringResource(
+ CredentialType.UNKNOWN -> stringResource(
R.string.choose_create_option_sign_in_title,
requestDisplayInfo.appName
)
@@ -828,7 +827,7 @@
Column() {
// TODO: Add the function to hide/view password when the type is create password
when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> {
+ CredentialType.PASSKEY -> {
TextOnSurfaceVariant(
text = requestDisplayInfo.title,
style = MaterialTheme.typography.titleLarge,
@@ -846,7 +845,7 @@
modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
)
}
- TYPE_PASSWORD_CREDENTIAL -> {
+ CredentialType.PASSWORD -> {
TextOnSurfaceVariant(
text = requestDisplayInfo.title,
style = MaterialTheme.typography.titleLarge,
@@ -859,7 +858,7 @@
modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
)
}
- else -> {
+ CredentialType.UNKNOWN -> {
if (requestDisplayInfo.subtitle != null) {
TextOnSurfaceVariant(
text = requestDisplayInfo.title,
@@ -917,8 +916,8 @@
modifier = Modifier.padding(start = 5.dp),
)
}
- if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL ||
- requestDisplayInfo.type == TYPE_PASSWORD_CREDENTIAL) {
+ if (requestDisplayInfo.type == CredentialType.PASSKEY ||
+ requestDisplayInfo.type == CredentialType.PASSWORD) {
if (createOptionInfo.passwordCount != null &&
createOptionInfo.passkeyCount != null
) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 12a5085..05be0a6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -21,6 +21,8 @@
import android.graphics.drawable.Drawable
import com.android.credentialmanager.common.DialogState
import com.android.credentialmanager.common.ProviderActivityState
+import com.android.credentialmanager.common.CredentialType
+import java.time.Instant
data class CreateCredentialUiState(
val enabledProviders: List<EnabledProviderInfo>,
@@ -68,18 +70,18 @@
)
class CreateOptionInfo(
- providerId: String,
- entryKey: String,
- entrySubkey: String,
- pendingIntent: PendingIntent?,
- fillInIntent: Intent?,
- val userProviderDisplayName: String?,
- val profileIcon: Drawable?,
- val passwordCount: Int?,
- val passkeyCount: Int?,
- val totalCredentialCount: Int?,
- val lastUsedTimeMillis: Long?,
- val footerDescription: String?,
+ providerId: String,
+ entryKey: String,
+ entrySubkey: String,
+ pendingIntent: PendingIntent?,
+ fillInIntent: Intent?,
+ val userProviderDisplayName: String?,
+ val profileIcon: Drawable?,
+ val passwordCount: Int?,
+ val passkeyCount: Int?,
+ val totalCredentialCount: Int?,
+ val lastUsedTime: Instant?,
+ val footerDescription: String?,
) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
class RemoteInfo(
@@ -93,7 +95,7 @@
data class RequestDisplayInfo(
val title: String,
val subtitle: String?,
- val type: String,
+ val type: CredentialType,
val appName: String,
val typeIcon: Drawable,
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index b30d1ec..a5e19b6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -16,7 +16,6 @@
package com.android.credentialmanager.getflow
-import android.credentials.Credential
import android.text.TextUtils
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
@@ -59,6 +58,7 @@
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.R
+import com.android.credentialmanager.common.CredentialType
import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
@@ -71,7 +71,6 @@
import com.android.credentialmanager.common.ui.TextOnSurfaceVariant
import com.android.credentialmanager.common.ui.ContainerCard
import com.android.credentialmanager.common.ui.TransparentBackgroundEntry
-import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
import com.android.credentialmanager.ui.theme.EntryShape
import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
@@ -171,7 +170,7 @@
) {
if (sortedUserNameToCredentialEntryList.first()
.sortedCredentialEntryList.first().credentialType
- == PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL
+ == CredentialType.PASSKEY
) R.string.get_dialog_title_use_passkey_for
else R.string.get_dialog_title_use_sign_in_for
} else if (
@@ -534,7 +533,7 @@
)
TextSecondary(
text = if (
- credentialEntryInfo.credentialType == Credential.TYPE_PASSWORD_CREDENTIAL) {
+ credentialEntryInfo.credentialType == CredentialType.PASSWORD) {
"••••••••••••"
} else {
if (TextUtils.isEmpty(credentialEntryInfo.displayName))
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 7d2f0da..5741f36 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -26,11 +26,11 @@
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.android.credentialmanager.CredentialManagerRepo
+import com.android.credentialmanager.common.CredentialType
import com.android.credentialmanager.common.Constants
import com.android.credentialmanager.common.DialogState
import com.android.credentialmanager.common.ProviderActivityResult
import com.android.credentialmanager.common.ProviderActivityState
-import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
import com.android.internal.util.Preconditions
data class GetCredentialUiState(
@@ -258,9 +258,9 @@
override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
// First prefer passkey type for its security benefits
if (p0.credentialType != p1.credentialType) {
- if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p0.credentialType) {
+ if (CredentialType.PASSKEY == p0.credentialType) {
return -1
- } else if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p1.credentialType) {
+ } else if (CredentialType.PASSKEY == p1.credentialType) {
return 1
}
}
@@ -272,9 +272,9 @@
} else if (p0.lastUsedTimeMillis > p1.lastUsedTimeMillis) {
return -1
}
- } else if (p0.lastUsedTimeMillis != null && p0.lastUsedTimeMillis > 0) {
+ } else if (p0.lastUsedTimeMillis != null) {
return -1
- } else if (p1.lastUsedTimeMillis != null && p1.lastUsedTimeMillis > 0) {
+ } else if (p1.lastUsedTimeMillis != null) {
return 1
}
return 0
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 8ce31bd..4c05dea 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -19,6 +19,9 @@
import android.app.PendingIntent
import android.content.Intent
import android.graphics.drawable.Drawable
+import com.android.credentialmanager.common.CredentialType
+
+import java.time.Instant
data class ProviderInfo(
/**
@@ -62,13 +65,13 @@
pendingIntent: PendingIntent?,
fillInIntent: Intent?,
/** Type of this credential used for sorting. Not localized so must not be directly displayed. */
- val credentialType: String,
+ val credentialType: CredentialType,
/** Localized type value of this credential used for display purpose. */
val credentialTypeDisplayName: String,
val userName: String,
val displayName: String?,
val icon: Drawable?,
- val lastUsedTimeMillis: Long?,
+ val lastUsedTimeMillis: Instant?,
) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
class AuthenticationEntryInfo(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
deleted file mode 100644
index eaa2ad4..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
+++ /dev/null
@@ -1,74 +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.credentialmanager.jetpack.developer
-
-import android.credentials.Credential
-import android.os.Bundle
-import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE
-
-/**
- * Base request class for registering a credential.
- *
- * @property type the credential type
- * @property data the request data in the [Bundle] format
- * @property requireSystemProvider true if must only be fulfilled by a system provider and false
- * otherwise
- */
-open class CreateCredentialRequest(
- open val type: String,
- open val credentialData: Bundle,
- open val candidateQueryData: Bundle,
- open val requireSystemProvider: Boolean
-) {
- companion object {
- @JvmStatic
- fun createFrom(
- type: String,
- credentialData: Bundle,
- candidateQueryData: Bundle,
- requireSystemProvider: Boolean
- ): CreateCredentialRequest {
- return try {
- when (type) {
- Credential.TYPE_PASSWORD_CREDENTIAL ->
- CreatePasswordRequest.createFrom(credentialData)
- PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
- when (credentialData.getString(BUNDLE_KEY_SUBTYPE)) {
- CreatePublicKeyCredentialRequest
- .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST ->
- CreatePublicKeyCredentialRequest.createFrom(credentialData)
- CreatePublicKeyCredentialRequestPrivileged
- .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV ->
- CreatePublicKeyCredentialRequestPrivileged
- .createFrom(credentialData)
- else -> throw FrameworkClassParsingException()
- }
- else -> throw FrameworkClassParsingException()
- }
- } catch (e: FrameworkClassParsingException) {
- // Parsing failed but don't crash the process. Instead just output a request with
- // the raw framework values.
- CreateCustomCredentialRequest(
- type,
- credentialData,
- candidateQueryData,
- requireSystemProvider
- )
- }
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCustomCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCustomCredentialRequest.kt
deleted file mode 100644
index 50da9a1..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCustomCredentialRequest.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.credentialmanager.jetpack.developer
-
-import android.os.Bundle
-
-/**
- * Base custom create request class for registering a credential.
- *
- * An application can construct a subtype custom request and call
- * [CredentialManager.executeCreateCredential] to launch framework UI flows to collect consent and
- * any other metadata needed from the user to register a new user credential.
- *
- * @property type the credential type determined by the credential-type-specific subclass for custom
- * use cases
- * @property credentialData the full credential creation request data in the [Bundle] format for
- * custom use cases
- * @property candidateQueryData the partial request data in the [Bundle] format that will be sent to
- * the provider during the initial candidate query stage, which should not contain sensitive user
- * credential information
- * @property requireSystemProvider true if must only be fulfilled by a system provider and false
- * otherwise
- * @throws IllegalArgumentException If [type] is empty
- * @throws NullPointerException If [type] or [credentialData] are null
- */
-open class CreateCustomCredentialRequest(
- final override val type: String,
- final override val credentialData: Bundle,
- final override val candidateQueryData: Bundle,
- @get:JvmName("requireSystemProvider")
- final override val requireSystemProvider: Boolean
-) : CreateCredentialRequest(type, credentialData, candidateQueryData, requireSystemProvider) {
- init {
- require(type.isNotEmpty()) { "type should not be empty" }
- }
-}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt
deleted file mode 100644
index bf0aa8a..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt
+++ /dev/null
@@ -1,76 +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.credentialmanager.jetpack.developer
-
-import android.credentials.Credential
-import android.os.Bundle
-
-/**
- * A request to save the user password credential with their password provider.
- *
- * @property id the user id associated with the password
- * @property password the password
- * @throws NullPointerException If [id] is null
- * @throws NullPointerException If [password] is null
- * @throws IllegalArgumentException If [password] is empty
- */
-class CreatePasswordRequest constructor(
- val id: String,
- val password: String,
-) : CreateCredentialRequest(
- type = Credential.TYPE_PASSWORD_CREDENTIAL,
- credentialData = toCredentialDataBundle(id, password),
- // No credential data should be sent during the query phase.
- candidateQueryData = Bundle(),
- requireSystemProvider = false,
-) {
-
- init {
- require(password.isNotEmpty()) { "password should not be empty" }
- }
-
- companion object {
- const val BUNDLE_KEY_ID = "androidx.credentials.BUNDLE_KEY_ID"
- const val BUNDLE_KEY_PASSWORD = "androidx.credentials.BUNDLE_KEY_PASSWORD"
-
- @JvmStatic
- internal fun toCredentialDataBundle(id: String, password: String): Bundle {
- val bundle = Bundle()
- bundle.putString(BUNDLE_KEY_ID, id)
- bundle.putString(BUNDLE_KEY_PASSWORD, password)
- return bundle
- }
-
- @JvmStatic
- internal fun toCandidateDataBundle(id: String): Bundle {
- val bundle = Bundle()
- bundle.putString(BUNDLE_KEY_ID, id)
- return bundle
- }
-
- @JvmStatic
- internal fun createFrom(data: Bundle): CreatePasswordRequest {
- try {
- val id = data.getString(BUNDLE_KEY_ID)
- val password = data.getString(BUNDLE_KEY_PASSWORD)
- return CreatePasswordRequest(id!!, password!!)
- } catch (e: Exception) {
- throw FrameworkClassParsingException()
- }
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt
deleted file mode 100644
index f3d402a..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt
+++ /dev/null
@@ -1,100 +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.credentialmanager.jetpack.developer
-
-import android.os.Bundle
-import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE
-
-/**
-* A request to register a passkey from the user's public key credential provider.
-*
-* @property requestJson the privileged request in JSON format in the standard webauthn web json
-* shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
-* @property preferImmediatelyAvailableCredentials true if you prefer the operation to return
-* immediately when there is no available passkey registration offering instead of falling back to
-* discovering remote options, and false (default) otherwise
-* @throws NullPointerException If [requestJson] is null
-* @throws IllegalArgumentException If [requestJson] is empty
-*/
-class CreatePublicKeyCredentialRequest @JvmOverloads constructor(
- val requestJson: String,
- @get:JvmName("preferImmediatelyAvailableCredentials")
- val preferImmediatelyAvailableCredentials: Boolean = false
-) : CreateCredentialRequest(
- type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
- credentialData = toCredentialDataBundle(requestJson, preferImmediatelyAvailableCredentials),
- // The whole request data should be passed during the query phase.
- candidateQueryData = toCredentialDataBundle(requestJson, preferImmediatelyAvailableCredentials),
- requireSystemProvider = false,
-) {
-
- init {
- require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
- }
-
- /** @hide */
- companion object {
- const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
- "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
- internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
- internal const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST =
- "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST"
-
- @JvmStatic
- internal fun toCredentialDataBundle(
- requestJson: String,
- preferImmediatelyAvailableCredentials: Boolean
- ): Bundle {
- val bundle = Bundle()
- bundle.putString(BUNDLE_KEY_SUBTYPE,
- BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST)
- bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
- bundle.putBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
- preferImmediatelyAvailableCredentials)
- return bundle
- }
-
- @JvmStatic
- internal fun toCandidateDataBundle(
- requestJson: String,
- preferImmediatelyAvailableCredentials: Boolean
- ): Bundle {
- val bundle = Bundle()
- bundle.putString(BUNDLE_KEY_SUBTYPE,
- BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST)
- bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
- bundle.putBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
- preferImmediatelyAvailableCredentials)
- return bundle
- }
-
- @Suppress("deprecation") // bundle.get() used for boolean value to prevent default
- // boolean value from being returned.
- @JvmStatic
- internal fun createFrom(data: Bundle): CreatePublicKeyCredentialRequest {
- try {
- val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
- val preferImmediatelyAvailableCredentials =
- data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
- return CreatePublicKeyCredentialRequest(requestJson!!,
- (preferImmediatelyAvailableCredentials!!) as Boolean)
- } catch (e: Exception) {
- throw FrameworkClassParsingException()
- }
- }
- }
-}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt
deleted file mode 100644
index 85ab2d2..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt
+++ /dev/null
@@ -1,183 +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.credentialmanager.jetpack.developer
-
-import android.os.Bundle
-import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE
-
-/**
- * A privileged request to register a passkey from the user’s public key credential provider, where
- * the caller can modify the rp. Only callers with privileged permission, e.g. user’s default
- * brower, caBLE, can use this. These permissions will be introduced in an upcoming release.
- * TODO("Add specific permission info/annotation")
- *
- * @property requestJson the privileged request in JSON format in the standard webauthn web json
- * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
- * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return
- * immediately when there is no available passkey registration offering instead of falling back to
- * discovering remote options, and false (default) otherwise
- * @property relyingParty the expected true RP ID which will override the one in the [requestJson],
- * where rp is defined [here](https://w3c.github.io/webauthn/#rp-id)
- * @property clientDataHash a hash that is used to verify the [relyingParty] Identity
- * @throws NullPointerException If any of [requestJson], [relyingParty], or [clientDataHash] is
- * null
- * @throws IllegalArgumentException If any of [requestJson], [relyingParty], or [clientDataHash] is
- * empty
- */
-class CreatePublicKeyCredentialRequestPrivileged @JvmOverloads constructor(
- val requestJson: String,
- val relyingParty: String,
- val clientDataHash: String,
- @get:JvmName("preferImmediatelyAvailableCredentials")
- val preferImmediatelyAvailableCredentials: Boolean = false
-) : CreateCredentialRequest(
- type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
- credentialData = toCredentialDataBundle(
- requestJson,
- relyingParty,
- clientDataHash,
- preferImmediatelyAvailableCredentials
- ),
- // The whole request data should be passed during the query phase.
- candidateQueryData = toCredentialDataBundle(
- requestJson, relyingParty, clientDataHash, preferImmediatelyAvailableCredentials
- ),
- requireSystemProvider = false,
-) {
-
- init {
- require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
- require(relyingParty.isNotEmpty()) { "rp must not be empty" }
- require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" }
- }
-
- /** A builder for [CreatePublicKeyCredentialRequestPrivileged]. */
- class Builder(
- private var requestJson: String,
- private var relyingParty: String,
- private var clientDataHash: String
- ) {
-
- private var preferImmediatelyAvailableCredentials: Boolean = false
-
- /**
- * Sets the privileged request in JSON format.
- */
- fun setRequestJson(requestJson: String): Builder {
- this.requestJson = requestJson
- return this
- }
-
- /**
- * Sets to true if you prefer the operation to return immediately when there is no available
- * passkey registration offering instead of falling back to discovering remote options, and
- * false otherwise.
- *
- * The default value is false.
- */
- @Suppress("MissingGetterMatchingBuilder")
- fun setPreferImmediatelyAvailableCredentials(
- preferImmediatelyAvailableCredentials: Boolean
- ): Builder {
- this.preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
- return this
- }
-
- /**
- * Sets the expected true RP ID which will override the one in the [requestJson].
- */
- fun setRelyingParty(relyingParty: String): Builder {
- this.relyingParty = relyingParty
- return this
- }
-
- /**
- * Sets a hash that is used to verify the [relyingParty] Identity.
- */
- fun setClientDataHash(clientDataHash: String): Builder {
- this.clientDataHash = clientDataHash
- return this
- }
-
- /** Builds a [CreatePublicKeyCredentialRequestPrivileged]. */
- fun build(): CreatePublicKeyCredentialRequestPrivileged {
- return CreatePublicKeyCredentialRequestPrivileged(
- this.requestJson,
- this.relyingParty, this.clientDataHash, this.preferImmediatelyAvailableCredentials
- )
- }
- }
-
- /** @hide */
- companion object {
- internal const val BUNDLE_KEY_RELYING_PARTY =
- "androidx.credentials.BUNDLE_KEY_RELYING_PARTY"
- internal const val BUNDLE_KEY_CLIENT_DATA_HASH =
- "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH"
- internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
- "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
-
- internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
-
- internal const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV =
- "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_" +
- "PRIVILEGED"
-
- @JvmStatic
- internal fun toCredentialDataBundle(
- requestJson: String,
- relyingParty: String,
- clientDataHash: String,
- preferImmediatelyAvailableCredentials: Boolean
- ): Bundle {
- val bundle = Bundle()
- bundle.putString(
- PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
- BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV
- )
- bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
- bundle.putString(BUNDLE_KEY_RELYING_PARTY, relyingParty)
- bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash)
- bundle.putBoolean(
- BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
- preferImmediatelyAvailableCredentials
- )
- return bundle
- }
-
- @Suppress("deprecation") // bundle.get() used for boolean value to prevent default
- // boolean value from being returned.
- @JvmStatic
- internal fun createFrom(data: Bundle): CreatePublicKeyCredentialRequestPrivileged {
- try {
- val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
- val rp = data.getString(BUNDLE_KEY_RELYING_PARTY)
- val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH)
- val preferImmediatelyAvailableCredentials =
- data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
- return CreatePublicKeyCredentialRequestPrivileged(
- requestJson!!,
- rp!!,
- clientDataHash!!,
- (preferImmediatelyAvailableCredentials!!) as Boolean,
- )
- } catch (e: Exception) {
- throw FrameworkClassParsingException()
- }
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt
deleted file mode 100644
index ee08e9e..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt
+++ /dev/null
@@ -1,27 +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.credentialmanager.jetpack.developer
-
-import android.os.Bundle
-
-/**
- * Base class for a credential with which the user consented to authenticate to the app.
- *
- * @property type the credential type
- * @property data the credential data in the [Bundle] format.
- */
-open class Credential(val type: String, val data: Bundle)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
deleted file mode 100644
index fc7b7de..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
+++ /dev/null
@@ -1,68 +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.credentialmanager.jetpack.developer
-
-import android.credentials.Credential
-import android.os.Bundle
-
-/**
- * Base request class for getting a registered credential.
- *
- * @property type the credential type
- * @property data the request data in the [Bundle] format
- * @property requireSystemProvider true if must only be fulfilled by a system provider and false
- * otherwise
- */
-open class GetCredentialOption(
- open val type: String,
- open val requestData: Bundle,
- open val candidateQueryData: Bundle,
- open val requireSystemProvider: Boolean,
-) {
- companion object {
- @JvmStatic
- fun createFrom(
- type: String,
- requestData: Bundle,
- candidateQueryData: Bundle,
- requireSystemProvider: Boolean
- ): GetCredentialOption {
- return try {
- when (type) {
- Credential.TYPE_PASSWORD_CREDENTIAL ->
- GetPasswordOption.createFrom(requestData)
- PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
- when (requestData.getString(PublicKeyCredential.BUNDLE_KEY_SUBTYPE)) {
- GetPublicKeyCredentialOption
- .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION ->
- GetPublicKeyCredentialOption.createFrom(requestData)
- GetPublicKeyCredentialOptionPrivileged
- .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED ->
- GetPublicKeyCredentialOptionPrivileged.createFrom(requestData)
- else -> throw FrameworkClassParsingException()
- }
- else -> throw FrameworkClassParsingException()
- }
- } catch (e: FrameworkClassParsingException) {
- // Parsing failed but don't crash the process. Instead just output a request with
- // the raw framework values.
- GetCustomCredentialOption(
- type, requestData, candidateQueryData, requireSystemProvider)
- }
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
deleted file mode 100644
index fdd57ff..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
+++ /dev/null
@@ -1,75 +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.credentialmanager.jetpack.developer
-
-/**
- * Encapsulates a request to get a user credential.
- *
- * @property getCredentialOptions the list of [GetCredentialOption] from which the user can choose
- * one to authenticate to the app
- * @throws IllegalArgumentException If [getCredentialOptions] is empty
- */
-class GetCredentialRequest constructor(
- val getCredentialOptions: List<GetCredentialOption>,
-) {
-
- init {
- require(getCredentialOptions.isNotEmpty()) { "credentialRequests should not be empty" }
- }
-
- /** A builder for [GetCredentialRequest]. */
- class Builder {
- private var getCredentialOptions: MutableList<GetCredentialOption> = mutableListOf()
-
- /** Adds a specific type of [GetCredentialOption]. */
- fun addGetCredentialOption(getCredentialOption: GetCredentialOption): Builder {
- getCredentialOptions.add(getCredentialOption)
- return this
- }
-
- /** Sets the list of [GetCredentialOption]. */
- fun setGetCredentialOptions(getCredentialOptions: List<GetCredentialOption>): Builder {
- this.getCredentialOptions = getCredentialOptions.toMutableList()
- return this
- }
-
- /**
- * Builds a [GetCredentialRequest].
- *
- * @throws IllegalArgumentException If [getCredentialOptions] is empty
- */
- fun build(): GetCredentialRequest {
- return GetCredentialRequest(getCredentialOptions.toList())
- }
- }
-
- companion object {
- @JvmStatic
- fun createFrom(from: android.credentials.GetCredentialRequest): GetCredentialRequest {
- return GetCredentialRequest(
- from.credentialOptions.map {
- GetCredentialOption.createFrom(
- it.type,
- it.credentialRetrievalData,
- it.candidateQueryData,
- it.isSystemProviderRequired()
- )
- }
- )
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCustomCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCustomCredentialOption.kt
deleted file mode 100644
index 803885c..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCustomCredentialOption.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.credentialmanager.jetpack.developer
-
-import android.os.Bundle
-
-/**
- * Allows extending custom versions of GetCredentialOptions for unique use cases.
- *
- * @property type the credential type determined by the credential-type-specific subclass
- * generated for custom use cases
- * @property requestData the request data in the [Bundle] format, generated for custom use cases
- * @property candidateQueryData the partial request data in the [Bundle] format that will be sent to
- * the provider during the initial candidate query stage, which should not contain sensitive user
- * information
- * @property requireSystemProvider true if must only be fulfilled by a system provider and false
- * otherwise
- * @throws IllegalArgumentException If [type] is empty
- * @throws NullPointerException If [requestData] or [type] is null
- */
-open class GetCustomCredentialOption(
- final override val type: String,
- final override val requestData: Bundle,
- final override val candidateQueryData: Bundle,
- @get:JvmName("requireSystemProvider")
- final override val requireSystemProvider: Boolean
-) : GetCredentialOption(
- type,
- requestData,
- candidateQueryData,
- requireSystemProvider
-) {
- init {
- require(type.isNotEmpty()) { "type should not be empty" }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt
deleted file mode 100644
index 2b9cfa3..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt
+++ /dev/null
@@ -1,35 +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.credentialmanager.jetpack.developer
-
-import android.credentials.Credential
-import android.os.Bundle
-
-/** A request to retrieve the user's saved application password from their password provider. */
-class GetPasswordOption : GetCredentialOption(
- Credential.TYPE_PASSWORD_CREDENTIAL,
- Bundle(),
- Bundle(),
- false,
-) {
- companion object {
- @JvmStatic
- fun createFrom(data: Bundle): GetPasswordOption {
- return GetPasswordOption()
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt
deleted file mode 100644
index 2f9b249..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt
+++ /dev/null
@@ -1,85 +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.credentialmanager.jetpack.developer
-
-import android.os.Bundle
-
-/**
- * A request to get passkeys from the user's public key credential provider.
- *
- * @property requestJson the privileged request in JSON format in the standard webauthn web json
- * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
- * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return
- * immediately when there is no available credential instead of falling back to discovering remote
- * credentials, and false (default) otherwise
- * @throws NullPointerException If [requestJson] is null
- * @throws IllegalArgumentException If [requestJson] is empty
- */
-class GetPublicKeyCredentialOption @JvmOverloads constructor(
- val requestJson: String,
- @get:JvmName("preferImmediatelyAvailableCredentials")
- val preferImmediatelyAvailableCredentials: Boolean = false,
-) : GetCredentialOption(
- type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
- requestData = toRequestDataBundle(requestJson, preferImmediatelyAvailableCredentials),
- candidateQueryData = toRequestDataBundle(requestJson, preferImmediatelyAvailableCredentials),
- requireSystemProvider = false
-) {
- init {
- require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
- }
-
- /** @hide */
- companion object {
- internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
- "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
- internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
- internal const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION =
- "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION"
-
- @JvmStatic
- internal fun toRequestDataBundle(
- requestJson: String,
- preferImmediatelyAvailableCredentials: Boolean
- ): Bundle {
- val bundle = Bundle()
- bundle.putString(
- PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
- BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION
- )
- bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
- bundle.putBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
- preferImmediatelyAvailableCredentials)
- return bundle
- }
-
- @Suppress("deprecation") // bundle.get() used for boolean value to prevent default
- // boolean value from being returned.
- @JvmStatic
- internal fun createFrom(data: Bundle): GetPublicKeyCredentialOption {
- try {
- val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
- val preferImmediatelyAvailableCredentials =
- data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
- return GetPublicKeyCredentialOption(requestJson!!,
- (preferImmediatelyAvailableCredentials!!) as Boolean)
- } catch (e: Exception) {
- throw FrameworkClassParsingException()
- }
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt
deleted file mode 100644
index 6f4782a..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt
+++ /dev/null
@@ -1,182 +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.credentialmanager.jetpack.developer
-
-import android.os.Bundle
-
-/**
- * A privileged request to get passkeys from the user's public key credential provider. The caller
- * can modify the RP. Only callers with privileged permission (e.g. user's public browser or caBLE)
- * can use this. These permissions will be introduced in an upcoming release.
- * TODO("Add specific permission info/annotation")
- *
- * @property requestJson the privileged request in JSON format in the standard webauthn web json
- * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
- * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return
- * immediately when there is no available credential instead of falling back to discovering remote
- * credentials, and false (default) otherwise
- * @property relyingParty the expected true RP ID which will override the one in the [requestJson],
- * where relyingParty is defined [here](https://w3c.github.io/webauthn/#rp-id) in more detail
- * @property clientDataHash a hash that is used to verify the [relyingParty] Identity
- * @throws NullPointerException If any of [requestJson], [relyingParty], or [clientDataHash]
- * is null
- * @throws IllegalArgumentException If any of [requestJson], [relyingParty], or [clientDataHash] is
- * empty
- */
-class GetPublicKeyCredentialOptionPrivileged @JvmOverloads constructor(
- val requestJson: String,
- val relyingParty: String,
- val clientDataHash: String,
- @get:JvmName("preferImmediatelyAvailableCredentials")
- val preferImmediatelyAvailableCredentials: Boolean = false
-) : GetCredentialOption(
- type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
- requestData = toBundle(
- requestJson,
- relyingParty,
- clientDataHash,
- preferImmediatelyAvailableCredentials
- ),
- candidateQueryData = toBundle(
- requestJson,
- relyingParty,
- clientDataHash,
- preferImmediatelyAvailableCredentials
- ),
- requireSystemProvider = false,
-) {
-
- init {
- require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
- require(relyingParty.isNotEmpty()) { "rp must not be empty" }
- require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" }
- }
-
- /** A builder for [GetPublicKeyCredentialOptionPrivileged]. */
- class Builder(
- private var requestJson: String,
- private var relyingParty: String,
- private var clientDataHash: String
- ) {
-
- private var preferImmediatelyAvailableCredentials: Boolean = false
-
- /**
- * Sets the privileged request in JSON format.
- */
- fun setRequestJson(requestJson: String): Builder {
- this.requestJson = requestJson
- return this
- }
-
- /**
- * Sets to true if you prefer the operation to return immediately when there is no available
- * credential instead of falling back to discovering remote credentials, and false
- * otherwise.
- *
- * The default value is false.
- */
- @Suppress("MissingGetterMatchingBuilder")
- fun setPreferImmediatelyAvailableCredentials(
- preferImmediatelyAvailableCredentials: Boolean
- ): Builder {
- this.preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
- return this
- }
-
- /**
- * Sets the expected true RP ID which will override the one in the [requestJson].
- */
- fun setRelyingParty(relyingParty: String): Builder {
- this.relyingParty = relyingParty
- return this
- }
-
- /**
- * Sets a hash that is used to verify the [relyingParty] Identity.
- */
- fun setClientDataHash(clientDataHash: String): Builder {
- this.clientDataHash = clientDataHash
- return this
- }
-
- /** Builds a [GetPublicKeyCredentialOptionPrivileged]. */
- fun build(): GetPublicKeyCredentialOptionPrivileged {
- return GetPublicKeyCredentialOptionPrivileged(
- this.requestJson,
- this.relyingParty, this.clientDataHash, this.preferImmediatelyAvailableCredentials
- )
- }
- }
-
- /** @hide */
- companion object {
- internal const val BUNDLE_KEY_RELYING_PARTY =
- "androidx.credentials.BUNDLE_KEY_RELYING_PARTY"
- internal const val BUNDLE_KEY_CLIENT_DATA_HASH =
- "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH"
- internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
- "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
- internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
- internal const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED =
- "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION" +
- "_PRIVILEGED"
-
- @JvmStatic
- internal fun toBundle(
- requestJson: String,
- relyingParty: String,
- clientDataHash: String,
- preferImmediatelyAvailableCredentials: Boolean
- ): Bundle {
- val bundle = Bundle()
- bundle.putString(
- PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
- BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED
- )
- bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
- bundle.putString(BUNDLE_KEY_RELYING_PARTY, relyingParty)
- bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash)
- bundle.putBoolean(
- BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
- preferImmediatelyAvailableCredentials
- )
- return bundle
- }
-
- @Suppress("deprecation") // bundle.get() used for boolean value to prevent default
- // boolean value from being returned.
- @JvmStatic
- internal fun createFrom(data: Bundle): GetPublicKeyCredentialOptionPrivileged {
- try {
- val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
- val rp = data.getString(BUNDLE_KEY_RELYING_PARTY)
- val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH)
- val preferImmediatelyAvailableCredentials =
- data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
- return GetPublicKeyCredentialOptionPrivileged(
- requestJson!!,
- rp!!,
- clientDataHash!!,
- (preferImmediatelyAvailableCredentials!!) as Boolean,
- )
- } catch (e: Exception) {
- throw FrameworkClassParsingException()
- }
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PasswordCredential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PasswordCredential.kt
deleted file mode 100644
index 1658858..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PasswordCredential.kt
+++ /dev/null
@@ -1,55 +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.credentialmanager.jetpack.developer
-
-import android.os.Bundle
-
-class PasswordCredential constructor(
- val id: String,
- val password: String,
-) : Credential(android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL, toBundle(id, password)) {
-
- init {
- require(password.isNotEmpty()) { "password should not be empty" }
- }
-
- /** @hide */
- companion object {
-
- const val BUNDLE_KEY_ID = "androidx.credentials.BUNDLE_KEY_ID"
- const val BUNDLE_KEY_PASSWORD = "androidx.credentials.BUNDLE_KEY_PASSWORD"
-
- @JvmStatic
- internal fun toBundle(id: String, password: String): Bundle {
- val bundle = Bundle()
- bundle.putString(BUNDLE_KEY_ID, id)
- bundle.putString(BUNDLE_KEY_PASSWORD, password)
- return bundle
- }
-
- @JvmStatic
- internal fun createFrom(data: Bundle): PasswordCredential {
- try {
- val id = data.getString(BUNDLE_KEY_ID)
- val password = data.getString(BUNDLE_KEY_PASSWORD)
- return PasswordCredential(id!!, password!!)
- } catch (e: Exception) {
- throw FrameworkClassParsingException()
- }
- }
- }
-}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt
deleted file mode 100644
index 6a81167..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.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.credentialmanager.jetpack.developer
-
-import android.os.Bundle
-
-/**
- * Represents the user's passkey credential granted by the user for app sign-in.
- *
- * @property authenticationResponseJson the public key credential authentication response in
- * JSON format that follows the standard webauthn json format shown at
- * [this w3c link](https://w3c.github.io/webauthn/#dictdef-authenticationresponsejson)
- * @throws NullPointerException If [authenticationResponseJson] is null. This is handled by the
- * kotlin runtime
- * @throws IllegalArgumentException If [authenticationResponseJson] is empty
- *
- * @hide
- */
-class PublicKeyCredential constructor(
- val authenticationResponseJson: String
-) : Credential(
- TYPE_PUBLIC_KEY_CREDENTIAL,
- toBundle(authenticationResponseJson)
-) {
-
- init {
- require(authenticationResponseJson.isNotEmpty()) {
- "authentication response JSON must not be empty" }
- }
- companion object {
- /** The type value for public key credential related operations. */
- const val TYPE_PUBLIC_KEY_CREDENTIAL: String =
- "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"
- /** The Bundle key value for the public key credential subtype (privileged or regular). */
- internal const val BUNDLE_KEY_SUBTYPE = "androidx.credentials.BUNDLE_KEY_SUBTYPE"
- const val BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON =
- "androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON"
-
- @JvmStatic
- internal fun toBundle(authenticationResponseJson: String): Bundle {
- val bundle = Bundle()
- bundle.putString(BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON, authenticationResponseJson)
- return bundle
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/Action.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/Action.kt
deleted file mode 100644
index 1abf911..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/Action.kt
+++ /dev/null
@@ -1,100 +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.credentialmanager.jetpack.provider
-
-import android.annotation.SuppressLint
-import android.app.PendingIntent
-import android.app.slice.Slice
-import android.app.slice.SliceSpec
-import android.net.Uri
-import android.util.Log
-import java.util.Collections
-
-/**
- * UI representation for a credential entry used during the get credential flow.
- *
- * TODO: move to jetpack.
- */
-class Action constructor(
- val title: CharSequence,
- val subTitle: CharSequence?,
- val pendingIntent: PendingIntent?,
-) {
-
- init {
- require(title.isNotEmpty()) { "title must not be empty" }
- }
-
- companion object {
- private const val TAG = "Action"
- internal const val SLICE_HINT_TITLE =
- "androidx.credentials.provider.action.HINT_ACTION_TITLE"
- internal const val SLICE_HINT_SUBTITLE =
- "androidx.credentials.provider.action.HINT_ACTION_SUBTEXT"
- internal const val SLICE_HINT_PENDING_INTENT =
- "androidx.credentials.provider.action.SLICE_HINT_PENDING_INTENT"
-
- @JvmStatic
- fun toSlice(action: Action): Slice {
- // TODO("Put the right spec and version value")
- val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec("type", 1))
- .addText(action.title, /*subType=*/null,
- listOf(SLICE_HINT_TITLE))
- .addText(action.subTitle, /*subType=*/null,
- listOf(SLICE_HINT_SUBTITLE))
- if (action.pendingIntent != null) {
- sliceBuilder.addAction(action.pendingIntent,
- Slice.Builder(sliceBuilder)
- .addHints(Collections.singletonList(SLICE_HINT_PENDING_INTENT))
- .build(),
- /*subType=*/null)
- }
- return sliceBuilder.build()
- }
-
- /**
- * Returns an instance of [Action] derived from a [Slice] object.
- *
- * @param slice the [Slice] object constructed through [toSlice]
- */
- @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
- @JvmStatic
- fun fromSlice(slice: Slice): Action? {
- // TODO("Put the right spec and version value")
- var title: CharSequence = ""
- var subTitle: CharSequence? = null
- var pendingIntent: PendingIntent? = null
-
- slice.items.forEach {
- if (it.hasHint(SLICE_HINT_TITLE)) {
- title = it.text
- } else if (it.hasHint(SLICE_HINT_SUBTITLE)) {
- subTitle = it.text
- } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
- pendingIntent = it.action
- }
- }
-
- return try {
- Action(title, subTitle, pendingIntent)
- } catch (e: Exception) {
- Log.i(TAG, "fromSlice failed with: " + e.message)
- null
- }
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/AuthenticationAction.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/AuthenticationAction.kt
deleted file mode 100644
index 283c7ba..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/AuthenticationAction.kt
+++ /dev/null
@@ -1,55 +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.credentialmanager.jetpack.provider
-
-import android.app.PendingIntent
-import android.app.slice.Slice
-import android.util.Log
-import androidx.annotation.VisibleForTesting
-
-/**
- * UI representation for a credential entry used during the get credential flow.
- *
- * TODO: move to jetpack.
- */
-class AuthenticationAction constructor(
- val pendingIntent: PendingIntent
-) {
-
-
- companion object {
- private const val TAG = "AuthenticationAction"
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- internal const val SLICE_HINT_PENDING_INTENT =
- "androidx.credentials.provider.authenticationAction.SLICE_HINT_PENDING_INTENT"
-
- @JvmStatic
- fun fromSlice(slice: Slice): AuthenticationAction? {
- slice.items.forEach {
- if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
- return try {
- AuthenticationAction(it.action)
- } catch (e: Exception) {
- Log.i(TAG, "fromSlice failed with: " + e.message)
- null
- }
- }
- }
- return null
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CreateEntry.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CreateEntry.kt
deleted file mode 100644
index 0ec91d6..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CreateEntry.kt
+++ /dev/null
@@ -1,243 +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.credentialmanager.jetpack.provider
-
-import android.annotation.SuppressLint
-import android.app.PendingIntent
-import android.app.slice.Slice
-import android.app.slice.SliceSpec
-import android.graphics.drawable.Icon
-import android.net.Uri
-import android.os.Bundle
-import android.util.Log
-import java.util.Collections
-
-/**
- * UI representation for a save entry used during the create credential flow.
- *
- * TODO: move to jetpack.
- */
-class CreateEntry internal constructor(
- val accountName: CharSequence,
- val pendingIntent: PendingIntent?,
- val icon: Icon?,
- val lastUsedTimeMillis: Long,
- val credentialCountInformationList: List<CredentialCountInformation>,
- val footerDescription: CharSequence?,
-) {
-
- init {
- require(accountName.isNotEmpty()) { "accountName must not be empty" }
- }
-
- /**
- * A builder for [CreateEntry]
- *
- * @property accountName the name of the account where the credential will be registered
- * @property pendingIntent the [PendingIntent] that will be fired when the user selects
- * this entry
- *
- * @hide
- */
- class Builder constructor(
- private val accountName: CharSequence,
- private val pendingIntent: PendingIntent? = null
- ) {
-
- private var credentialCountInformationList: MutableList<CredentialCountInformation> =
- mutableListOf()
- private var icon: Icon? = null
- private var lastUsedTimeMillis: Long = 0
- private var footerDescription: CharSequence? = null
-
- /** Adds a [CredentialCountInformation] denoting a given credential
- * type and the count of credentials that the provider has stored for that
- * credential type.
- *
- * This information will be displayed on the [CreateEntry] to help the user
- * make a choice.
- */
- @Suppress("MissingGetterMatchingBuilder")
- fun addCredentialCountInformation(info: CredentialCountInformation): Builder {
- credentialCountInformationList.add(info)
- return this
- }
-
- /** Sets a list of [CredentialCountInformation]. Each item in the list denotes a given
- * credential type and the count of credentials that the provider has stored of that
- * credential type.
- *
- * This information will be displayed on the [CreateEntry] to help the user
- * make a choice.
- */
- fun setCredentialCountInformationList(infoList: List<CredentialCountInformation>): Builder {
- credentialCountInformationList = infoList as MutableList<CredentialCountInformation>
- return this
- }
-
- /** Sets an icon to be displayed with the entry on the UI */
- fun setIcon(icon: Icon?): Builder {
- this.icon = icon
- return this
- }
-
- /** Sets the last time this account was used */
- fun setLastUsedTimeMillis(lastUsedTimeMillis: Long): Builder {
- this.lastUsedTimeMillis = lastUsedTimeMillis
- return this
- }
-
- /** Sets the footer description of this */
- fun setFooterDescription(footerDescription: CharSequence): Builder {
- this.footerDescription = footerDescription
- return this
- }
-
- /**
- * Builds an instance of [CreateEntry]
- *
- * @throws IllegalArgumentException If [accountName] is empty
- */
- fun build(): CreateEntry {
- return CreateEntry(accountName, pendingIntent, icon, lastUsedTimeMillis,
- credentialCountInformationList, footerDescription)
- }
- }
-
- companion object {
- private const val TAG = "CreateEntry"
- internal const val SLICE_HINT_ACCOUNT_NAME =
- "androidx.credentials.provider.createEntry.SLICE_HINT_USER_PROVIDER_ACCOUNT_NAME"
- internal const val SLICE_HINT_ICON =
- "androidx.credentials.provider.createEntry.SLICE_HINT_PROFILE_ICON"
- internal const val SLICE_HINT_CREDENTIAL_COUNT_INFORMATION =
- "androidx.credentials.provider.createEntry.SLICE_HINT_CREDENTIAL_COUNT_INFORMATION"
- internal const val SLICE_HINT_LAST_USED_TIME_MILLIS =
- "androidx.credentials.provider.createEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
- internal const val SLICE_HINT_PENDING_INTENT =
- "androidx.credentials.provider.createEntry.SLICE_HINT_PENDING_INTENT"
- internal const val SLICE_HINT_FOOTER_DESCRIPTION =
- "androidx.credentials.provider.createEntry.SLICE_HINT_FOOTER_DESCRIPTION"
-
- @JvmStatic
- fun toSlice(createEntry: CreateEntry): Slice {
- // TODO("Use the right type and revision")
- val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec("type", 1))
- sliceBuilder.addText(createEntry.accountName, /*subType=*/null,
- listOf(SLICE_HINT_ACCOUNT_NAME))
- .addLong(createEntry.lastUsedTimeMillis, /*subType=*/null, listOf(
- SLICE_HINT_LAST_USED_TIME_MILLIS))
- if (createEntry.icon != null) {
- sliceBuilder.addIcon(createEntry.icon, /*subType=*/null,
- listOf(SLICE_HINT_ICON))
- }
-
- val credentialCountBundle = convertCredentialCountInfoToBundle(
- createEntry.credentialCountInformationList)
- if (credentialCountBundle != null) {
- sliceBuilder.addBundle(convertCredentialCountInfoToBundle(
- createEntry.credentialCountInformationList), null, listOf(
- SLICE_HINT_CREDENTIAL_COUNT_INFORMATION))
- }
- if (createEntry.pendingIntent != null) {
- sliceBuilder.addAction(createEntry.pendingIntent,
- Slice.Builder(sliceBuilder)
- .addHints(Collections.singletonList(SLICE_HINT_PENDING_INTENT))
- .build(),
- /*subType=*/null)
- }
- if (createEntry.footerDescription != null) {
- sliceBuilder.addText(createEntry.footerDescription, /*subType=*/null,
- listOf(SLICE_HINT_FOOTER_DESCRIPTION))
- }
- return sliceBuilder.build()
- }
-
- /**
- * Returns an instance of [CreateEntry] derived from a [Slice] object.
- *
- * @param slice the [Slice] object constructed through [toSlice]
- */
- @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
- @JvmStatic
- fun fromSlice(slice: Slice): CreateEntry? {
- // TODO("Put the right spec and version value")
- var accountName: CharSequence = ""
- var icon: Icon? = null
- var pendingIntent: PendingIntent? = null
- var credentialCountInfo: List<CredentialCountInformation> = listOf()
- var lastUsedTimeMillis: Long = 0
- var footerDescription: CharSequence? = null
-
- slice.items.forEach {
- if (it.hasHint(SLICE_HINT_ACCOUNT_NAME)) {
- accountName = it.text
- } else if (it.hasHint(SLICE_HINT_ICON)) {
- icon = it.icon
- } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
- pendingIntent = it.action
- } else if (it.hasHint(SLICE_HINT_CREDENTIAL_COUNT_INFORMATION)) {
- credentialCountInfo = convertBundleToCredentialCountInfo(it.bundle)
- } else if (it.hasHint(SLICE_HINT_LAST_USED_TIME_MILLIS)) {
- lastUsedTimeMillis = it.long
- } else if (it.hasHint(SLICE_HINT_FOOTER_DESCRIPTION)) {
- footerDescription = it.text
- }
- }
-
- return try {
- CreateEntry(accountName, pendingIntent, icon,
- lastUsedTimeMillis, credentialCountInfo, footerDescription)
- } catch (e: Exception) {
- Log.i(TAG, "fromSlice failed with: " + e.message)
- null
- }
- }
-
- @JvmStatic
- internal fun convertBundleToCredentialCountInfo(bundle: Bundle?):
- List<CredentialCountInformation> {
- val credentialCountList = ArrayList<CredentialCountInformation>()
- if (bundle == null) {
- return credentialCountList
- }
- bundle.keySet().forEach {
- try {
- credentialCountList.add(
- CredentialCountInformation(it, bundle.getInt(it)))
- } catch (e: Exception) {
- Log.i(TAG, "Issue unpacking credential count info bundle: " + e.message)
- }
- }
- return credentialCountList
- }
-
- @JvmStatic
- internal fun convertCredentialCountInfoToBundle(
- credentialCountInformationList: List<CredentialCountInformation>
- ): Bundle? {
- if (credentialCountInformationList.isEmpty()) {
- return null
- }
- val bundle = Bundle()
- credentialCountInformationList.forEach {
- bundle.putInt(it.type, it.count)
- }
- return bundle
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialCountInformation.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialCountInformation.kt
deleted file mode 100644
index aa77b74..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialCountInformation.kt
+++ /dev/null
@@ -1,61 +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.credentialmanager.jetpack.provider
-
-import android.credentials.Credential
-import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
-
-class CredentialCountInformation constructor(
- val type: String,
- val count: Int
-) {
- companion object {
- @JvmStatic
- fun createPasswordCountInformation(count: Int): CredentialCountInformation {
- return CredentialCountInformation(Credential.TYPE_PASSWORD_CREDENTIAL, count)
- }
-
- @JvmStatic
- fun getPasswordCount(infos: List<CredentialCountInformation>): Int? {
- return getCountForType(infos, Credential.TYPE_PASSWORD_CREDENTIAL)
- }
-
- @JvmStatic
- fun createPublicKeyCountInformation(count: Int): CredentialCountInformation {
- return CredentialCountInformation(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, count)
- }
-
- @JvmStatic
- fun getPasskeyCount(infos: List<CredentialCountInformation>): Int? {
- return getCountForType(infos, PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL)
- }
-
- @JvmStatic
- fun createTotalCountInformation(count: Int): CredentialCountInformation {
- return CredentialCountInformation("TOTAL_COUNT", count)
- }
-
- @JvmStatic
- fun getTotalCount(infos: List<CredentialCountInformation>): Int? {
- return getCountForType(infos, "TOTAL_COUNT")
- }
-
- private fun getCountForType(infos: List<CredentialCountInformation>, type: String): Int? {
- return infos.firstOrNull { info -> info.type == type }?.count
- }
- }
-}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntry.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntry.kt
deleted file mode 100644
index 61a104b..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntry.kt
+++ /dev/null
@@ -1,150 +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.credentialmanager.jetpack.provider
-
-import android.annotation.SuppressLint
-import android.app.PendingIntent
-import android.app.slice.Slice
-import android.app.slice.SliceSpec
-import android.graphics.drawable.Icon
-import android.net.Uri
-import android.util.Log
-import java.util.Collections
-
-/**
- * UI representation for a credential entry used during the get credential flow.
- *
- * TODO: move to jetpack.
- */
-open class CredentialEntry constructor(
- // TODO("Add credential type display name for both CredentialEntry & CreateEntry")
- val type: String,
- val typeDisplayName: CharSequence,
- val username: CharSequence,
- val displayName: CharSequence?,
- val pendingIntent: PendingIntent?,
- // TODO("Consider using Instant or other strongly typed time data type")
- val lastUsedTimeMillis: Long,
- val icon: Icon?,
- var autoSelectAllowed: Boolean
-) {
- init {
- require(type.isNotEmpty()) { "type must not be empty" }
- require(username.isNotEmpty()) { "type must not be empty" }
- }
-
- companion object {
- private const val TAG = "CredentialEntry"
- internal const val SLICE_HINT_TYPE_DISPLAY_NAME =
- "androidx.credentials.provider.credentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
- internal const val SLICE_HINT_USERNAME =
- "androidx.credentials.provider.credentialEntry.SLICE_HINT_USER_NAME"
- internal const val SLICE_HINT_DISPLAYNAME =
- "androidx.credentials.provider.credentialEntry.SLICE_HINT_CREDENTIAL_TYPE_DISPLAY_NAME"
- internal const val SLICE_HINT_LAST_USED_TIME_MILLIS =
- "androidx.credentials.provider.credentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
- internal const val SLICE_HINT_ICON =
- "androidx.credentials.provider.credentialEntry.SLICE_HINT_PROFILE_ICON"
- internal const val SLICE_HINT_PENDING_INTENT =
- "androidx.credentials.provider.credentialEntry.SLICE_HINT_PENDING_INTENT"
- internal const val SLICE_HINT_AUTO_ALLOWED =
- "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_ALLOWED"
- internal const val AUTO_SELECT_TRUE_STRING = "true"
- internal const val AUTO_SELECT_FALSE_STRING = "false"
-
- @JvmStatic
- internal fun toSlice(credentialEntry: CredentialEntry): Slice {
- // TODO("Put the right revision value")
- val autoSelectAllowed = if (credentialEntry.autoSelectAllowed) {
- AUTO_SELECT_TRUE_STRING
- } else {
- AUTO_SELECT_FALSE_STRING
- }
- val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec(
- credentialEntry.type, 1))
- .addText(credentialEntry.typeDisplayName, /*subType=*/null,
- listOf(SLICE_HINT_TYPE_DISPLAY_NAME))
- .addText(credentialEntry.username, /*subType=*/null,
- listOf(SLICE_HINT_USERNAME))
- .addText(credentialEntry.displayName, /*subType=*/null,
- listOf(SLICE_HINT_DISPLAYNAME))
- .addLong(credentialEntry.lastUsedTimeMillis, /*subType=*/null,
- listOf(SLICE_HINT_LAST_USED_TIME_MILLIS))
- .addText(autoSelectAllowed, /*subType=*/null,
- listOf(SLICE_HINT_AUTO_ALLOWED))
- if (credentialEntry.icon != null) {
- sliceBuilder.addIcon(credentialEntry.icon, /*subType=*/null,
- listOf(SLICE_HINT_ICON))
- }
- if (credentialEntry.pendingIntent != null) {
- sliceBuilder.addAction(credentialEntry.pendingIntent,
- Slice.Builder(sliceBuilder)
- .addHints(Collections.singletonList(SLICE_HINT_PENDING_INTENT))
- .build(),
- /*subType=*/null)
- }
- return sliceBuilder.build()
- }
-
- /**
- * Returns an instance of [CredentialEntry] derived from a [Slice] object.
- *
- * @param slice the [Slice] object constructed through [toSlice]
- */
- @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
- @JvmStatic
- fun fromSlice(slice: Slice): CredentialEntry? {
- var typeDisplayName: CharSequence? = null
- var username: CharSequence? = null
- var displayName: CharSequence? = null
- var icon: Icon? = null
- var pendingIntent: PendingIntent? = null
- var lastUsedTimeMillis: Long = 0
- var autoSelectAllowed = false
-
- slice.items.forEach {
- if (it.hasHint(SLICE_HINT_TYPE_DISPLAY_NAME)) {
- typeDisplayName = it.text
- } else if (it.hasHint(SLICE_HINT_USERNAME)) {
- username = it.text
- } else if (it.hasHint(SLICE_HINT_DISPLAYNAME)) {
- displayName = it.text
- } else if (it.hasHint(SLICE_HINT_ICON)) {
- icon = it.icon
- } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
- pendingIntent = it.action
- } else if (it.hasHint(SLICE_HINT_LAST_USED_TIME_MILLIS)) {
- lastUsedTimeMillis = it.long
- } else if (it.hasHint(SLICE_HINT_AUTO_ALLOWED)) {
- val autoSelectValue = it.text
- if (autoSelectValue == AUTO_SELECT_TRUE_STRING) {
- autoSelectAllowed = true
- }
- }
- }
-
- return try {
- CredentialEntry(slice.spec!!.type, typeDisplayName!!, username!!,
- displayName, pendingIntent,
- lastUsedTimeMillis, icon, autoSelectAllowed)
- } catch (e: Exception) {
- Log.i(TAG, "fromSlice failed with: " + e.message)
- null
- }
- }
- }
-}
diff --git a/packages/SettingsLib/ActivityEmbedding/OWNERS b/packages/SettingsLib/ActivityEmbedding/OWNERS
new file mode 100644
index 0000000..7022402
--- /dev/null
+++ b/packages/SettingsLib/ActivityEmbedding/OWNERS
@@ -0,0 +1,5 @@
+# Default reviewers for this and subdirectories.
+arcwang@google.com
+chiujason@google.com
+
+# Emergency approvers in case the above are not available
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index 36315b5..1b3020a 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -1,12 +1,11 @@
# People who can approve changes for submission
dsandler@android.com
edgarwang@google.com
-emilychuang@google.com
evanlaird@google.com
-hanxu@google.com
juliacr@google.com
+lijun@google.com
+songchenxi@google.com
yantingyang@google.com
-ykhung@google.com
# Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
per-file *.xml=*
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
index eeab085..69c4705 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
@@ -46,7 +46,7 @@
object ChartPageProvider : SettingsPageProvider {
override val name = "Chart"
- val owner = createSettingsPage()
+ private val owner = createSettingsPage()
override fun getTitle(arguments: Bundle?): String {
return TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
index 2328fcb..9c7e0ce 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
@@ -37,7 +37,7 @@
object FooterPageProvider : SettingsPageProvider {
override val name = "Footer"
- val owner = createSettingsPage()
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
index 45b7989..ee22b96 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
@@ -36,7 +36,7 @@
object IllustrationPageProvider : SettingsPageProvider {
override val name = "Illustration"
- val owner = createSettingsPage()
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
index 74e49a6..1051549 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
@@ -42,7 +42,7 @@
object SliderPageProvider : SettingsPageProvider {
override val name = "Slider"
- val owner = createSettingsPage()
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
index d100d9d..442ace8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
@@ -38,7 +38,7 @@
object MainSwitchPreferencePageProvider : SettingsPageProvider {
override val name = "MainSwitchPreference"
- val owner = createSettingsPage()
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
index 6ad4bd8..b67e066 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
@@ -43,7 +43,7 @@
object SwitchPreferencePageProvider : SettingsPageProvider {
override val name = "SwitchPreference"
- val owner = createSettingsPage()
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
index 770f9a0..a2cd283 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
@@ -40,7 +40,7 @@
object TwoTargetSwitchPreferencePageProvider : SettingsPageProvider {
override val name = "TwoTargetSwitchPreference"
- val owner = createSettingsPage()
+ private val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
index 838c0cf..494e3cc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
@@ -161,7 +161,7 @@
.add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute())
.add(ColumnEnum.PAGE_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
.add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size)
- .add(ColumnEnum.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0)
+ .add(ColumnEnum.PAGE_BROWSABLE.id, if (page.isBrowsable()) 1 else 0)
}
return cursor
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
index bb9a134..fc6160e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
@@ -28,7 +28,7 @@
PAGE_ROUTE("pageRoute"),
PAGE_INTENT_URI("pageIntent"),
PAGE_ENTRY_COUNT("entryCount"),
- HAS_RUNTIME_PARAM("hasRuntimeParam"),
+ PAGE_BROWSABLE("pageBrowsable"),
PAGE_START_ADB("pageStartAdb"),
// Columns related to entry
@@ -67,7 +67,7 @@
ColumnEnum.PAGE_ROUTE,
ColumnEnum.PAGE_INTENT_URI,
ColumnEnum.PAGE_ENTRY_COUNT,
- ColumnEnum.HAS_RUNTIME_PARAM,
+ ColumnEnum.PAGE_BROWSABLE,
)
),
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index bccd8aa..f1b1abd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -39,7 +39,7 @@
import androidx.navigation.NavGraph.Companion.findStartDestination
import com.android.settingslib.spa.R
import com.android.settingslib.spa.framework.common.LogCategory
-import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.NullPageProvider
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
@@ -106,14 +106,13 @@
@Composable
private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<SettingsPageProvider>) {
- val nullPage = SettingsPage.createNull()
AnimatedNavHost(
navController = navController,
- startDestination = nullPage.sppName,
+ startDestination = NullPageProvider.name,
) {
val slideEffect = tween<IntOffset>(durationMillis = 300)
val fadeEffect = tween<Float>(durationMillis = 300)
- composable(nullPage.sppName) {}
+ composable(NullPageProvider.name) {}
for (spp in allProvider) {
composable(
route = spp.name + spp.parameter.navRoute(),
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 44714ab..4b73e94 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -24,6 +24,7 @@
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import com.android.settingslib.spa.framework.compose.LocalNavController
+import com.android.settingslib.spa.framework.util.genEntryId
private const val INJECT_ENTRY_NAME = "INJECT"
private const val ROOT_ENTRY_NAME = "ROOT"
@@ -188,7 +189,7 @@
val page = fromPage ?: owner
val isEnabled = page.isEnabled()
return SettingsEntry(
- id = id(),
+ id = genEntryId(name, owner, fromPage, toPage),
name = name,
owner = owner,
displayName = displayName,
@@ -274,11 +275,6 @@
return this
}
- // The unique id of this entry, which is computed by name + owner + fromPage + toPage.
- private fun id(): String {
- return "$name:${owner.id}(${fromPage?.id}-${toPage?.id})".toHashId()
- }
-
companion object {
fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
return SettingsEntryBuilder(entryName, owner)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index 14b1629..14dc785 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -49,7 +49,7 @@
entryMap = mutableMapOf()
pageWithEntryMap = mutableMapOf()
- val nullPage = SettingsPage.createNull()
+ val nullPage = NullPageProvider.createSettingsPage()
val entryQueue = LinkedList<SettingsEntry>()
for (page in sppRepository.getAllRootPages()) {
val rootEntry =
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 94cfcc2..c810648 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -19,12 +19,9 @@
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.util.genPageId
import com.android.settingslib.spa.framework.util.isRuntimeParam
import com.android.settingslib.spa.framework.util.navLink
-import com.android.settingslib.spa.framework.util.normalize
-import com.android.settingslib.spa.framework.util.normalizeArgList
-
-private const val NULL_PAGE_NAME = "NULL"
/**
* Defines data to identify a Settings page.
@@ -46,10 +43,7 @@
val arguments: Bundle? = null,
) {
companion object {
- fun createNull(): SettingsPage {
- return create(NULL_PAGE_NAME)
- }
-
+ // TODO: cleanup it once all its usage in Settings are switched to Spp.createSettingsPage
fun create(
name: String,
displayName: String? = null,
@@ -57,23 +51,13 @@
arguments: Bundle? = null
): SettingsPage {
return SettingsPage(
- id = id(name, parameter, arguments),
+ id = genPageId(name, parameter, arguments),
sppName = name,
displayName = displayName ?: name,
parameter = parameter,
arguments = arguments
)
}
-
- // The unique id of this page, which is computed by name + normalized(arguments)
- private fun id(
- name: String,
- parameter: List<NamedNavArgument> = emptyList(),
- arguments: Bundle? = null
- ): String {
- val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
- return "$name:${normArguments?.toString()}".toHashId()
- }
}
// Returns if this Settings Page is created by the given Spp.
@@ -85,43 +69,20 @@
return sppName + parameter.navLink(arguments)
}
- fun hasRuntimeParam(): Boolean {
- for (navArg in parameter) {
- if (navArg.isRuntimeParam()) return true
- }
- return false
- }
-
fun isBrowsable(): Boolean {
- return !isCreateBy(NULL_PAGE_NAME) && !hasRuntimeParam()
- }
-
- private fun getProvider(): SettingsPageProvider? {
- if (!SpaEnvironmentFactory.isReady()) return null
- val pageProviderRepository by SpaEnvironmentFactory.instance.pageProviderRepository
- return pageProviderRepository.getProviderOrNull(sppName)
+ if (sppName == NullPageProvider.name) return false
+ for (navArg in parameter) {
+ if (navArg.isRuntimeParam()) return false
+ }
+ return true
}
fun isEnabled(): Boolean {
- return getProvider()?.isEnabled(arguments) ?: false
+ return getPageProvider(sppName)?.isEnabled(arguments) ?: false
}
@Composable
fun UiLayout() {
- getProvider()?.Page(arguments)
+ getPageProvider(sppName)?.Page(arguments)
}
}
-
-fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
- return SettingsPage.create(
- name = name,
- displayName = displayName + parameter.normalizeArgList(arguments, eraseRuntimeValues = true)
- .joinToString("") { arg -> "/$arg" },
- parameter = parameter,
- arguments = arguments,
- )
-}
-
-fun String.toHashId(): String {
- return this.hashCode().toUInt().toString(36)
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index c564130..18f964e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -20,8 +20,12 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.util.genPageId
+import com.android.settingslib.spa.framework.util.normalizeArgList
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+private const val NULL_PAGE_NAME = "NULL"
+
/**
* An SettingsPageProvider which is used to create Settings page instances.
*/
@@ -62,3 +66,24 @@
}
}
}
+
+fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
+ return SettingsPage(
+ id = genPageId(name, parameter, arguments),
+ sppName = name,
+ displayName = displayName + parameter.normalizeArgList(arguments, eraseRuntimeValues = true)
+ .joinToString("") { arg -> "/$arg" },
+ parameter = parameter,
+ arguments = arguments,
+ )
+}
+
+object NullPageProvider : SettingsPageProvider {
+ override val name = NULL_PAGE_NAME
+}
+
+fun getPageProvider(sppName: String): SettingsPageProvider? {
+ if (!SpaEnvironmentFactory.isReady()) return null
+ val pageProviderRepository by SpaEnvironmentFactory.instance.pageProviderRepository
+ return pageProviderRepository.getProviderOrNull(sppName)
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/UniqueId.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/UniqueId.kt
new file mode 100644
index 0000000..3b0ff7d
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/UniqueId.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.settingslib.spa.framework.util
+
+import android.os.Bundle
+import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.common.SettingsPage
+
+fun genPageId(
+ sppName: String,
+ parameter: List<NamedNavArgument> = emptyList(),
+ arguments: Bundle? = null
+): String {
+ val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
+ return "$sppName:${normArguments?.toString()}".toHashId()
+}
+
+fun genEntryId(
+ name: String,
+ owner: SettingsPage,
+ fromPage: SettingsPage? = null,
+ toPage: SettingsPage? = null
+): String {
+ return "$name:${owner.id}(${fromPage?.id}-${toPage?.id})".toHashId()
+}
+
+// TODO: implement a better hash function
+private fun String.toHashId(): String {
+ return this.hashCode().toUInt().toString(36)
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
index c0b7464..730aa8f 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -19,12 +19,12 @@
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.util.genEntryId
+import com.android.settingslib.spa.framework.util.genPageId
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.tests.testutils.SppLayer1
import com.android.settingslib.spa.tests.testutils.SppLayer2
-import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
-import com.android.settingslib.spa.tests.testutils.getUniquePageId
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,18 +41,18 @@
val pageWithEntry = entryRepository.getAllPageWithEntry()
assertThat(pageWithEntry.size).isEqualTo(3)
assertThat(
- entryRepository.getPageWithEntry(getUniquePageId("SppHome"))
+ entryRepository.getPageWithEntry(genPageId("SppHome"))
?.entries?.size
).isEqualTo(1)
assertThat(
- entryRepository.getPageWithEntry(getUniquePageId("SppLayer1"))
+ entryRepository.getPageWithEntry(genPageId("SppLayer1"))
?.entries?.size
).isEqualTo(3)
assertThat(
- entryRepository.getPageWithEntry(getUniquePageId("SppLayer2"))
+ entryRepository.getPageWithEntry(genPageId("SppLayer2"))
?.entries?.size
).isEqualTo(2)
- assertThat(entryRepository.getPageWithEntry(getUniquePageId("SppWithParam"))).isNull()
+ assertThat(entryRepository.getPageWithEntry(genPageId("SppWithParam"))).isNull()
}
@Test
@@ -61,17 +61,17 @@
assertThat(entry.size).isEqualTo(7)
assertThat(
entryRepository.getEntry(
- getUniqueEntryId(
+ genEntryId(
"ROOT",
SppHome.createSettingsPage(),
- SettingsPage.createNull(),
+ NullPageProvider.createSettingsPage(),
SppHome.createSettingsPage(),
)
)
).isNotNull()
assertThat(
entryRepository.getEntry(
- getUniqueEntryId(
+ genEntryId(
"INJECT",
SppLayer1.createSettingsPage(),
SppHome.createSettingsPage(),
@@ -81,7 +81,7 @@
).isNotNull()
assertThat(
entryRepository.getEntry(
- getUniqueEntryId(
+ genEntryId(
"INJECT",
SppLayer2.createSettingsPage(),
SppLayer1.createSettingsPage(),
@@ -91,22 +91,22 @@
).isNotNull()
assertThat(
entryRepository.getEntry(
- getUniqueEntryId("Layer1Entry1", SppLayer1.createSettingsPage())
+ genEntryId("Layer1Entry1", SppLayer1.createSettingsPage())
)
).isNotNull()
assertThat(
entryRepository.getEntry(
- getUniqueEntryId("Layer1Entry2", SppLayer1.createSettingsPage())
+ genEntryId("Layer1Entry2", SppLayer1.createSettingsPage())
)
).isNotNull()
assertThat(
entryRepository.getEntry(
- getUniqueEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
+ genEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
)
).isNotNull()
assertThat(
entryRepository.getEntry(
- getUniqueEntryId("Layer2Entry2", SppLayer2.createSettingsPage())
+ genEntryId("Layer2Entry2", SppLayer2.createSettingsPage())
)
).isNotNull()
}
@@ -115,21 +115,21 @@
fun testGetEntryPath() {
assertThat(
entryRepository.getEntryPathWithDisplayName(
- getUniqueEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
+ genEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
)
).containsExactly("Layer2Entry1", "INJECT_SppLayer2", "INJECT_SppLayer1", "ROOT_SppHome")
.inOrder()
assertThat(
entryRepository.getEntryPathWithTitle(
- getUniqueEntryId("Layer2Entry2", SppLayer2.createSettingsPage()),
+ genEntryId("Layer2Entry2", SppLayer2.createSettingsPage()),
"entryTitle"
)
).containsExactly("entryTitle", "SppLayer2", "TitleLayer1", "TitleHome").inOrder()
assertThat(
entryRepository.getEntryPathWithDisplayName(
- getUniqueEntryId(
+ genEntryId(
"INJECT",
SppLayer1.createSettingsPage(),
SppHome.createSettingsPage(),
@@ -140,7 +140,7 @@
assertThat(
entryRepository.getEntryPathWithTitle(
- getUniqueEntryId(
+ genEntryId(
"INJECT",
SppLayer2.createSettingsPage(),
SppLayer1.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
index 6de1ae5..5754c9b 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -23,11 +23,12 @@
import androidx.core.os.bundleOf
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.util.genEntryId
+import com.android.settingslib.spa.framework.util.genPageId
import com.android.settingslib.spa.slice.appendSpaParams
import com.android.settingslib.spa.slice.getEntryId
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
-import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
-import com.android.settingslib.spa.tests.testutils.getUniquePageId
+import com.android.settingslib.spa.tests.testutils.createSettingsPage
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -64,9 +65,9 @@
@Test
fun testBuildBasic() {
- val owner = SettingsPage.create("mySpp")
+ val owner = createSettingsPage("mySpp")
val entry = SettingsEntryBuilder.create(owner, "myEntry").build()
- assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+ assertThat(entry.id).isEqualTo(genEntryId("myEntry", owner))
assertThat(entry.displayName).isEqualTo("myEntry")
assertThat(entry.owner.sppName).isEqualTo("mySpp")
assertThat(entry.owner.displayName).isEqualTo("mySpp")
@@ -80,19 +81,19 @@
@Test
fun testBuildWithLink() {
- val owner = SettingsPage.create("mySpp")
- val fromPage = SettingsPage.create("fromSpp")
- val toPage = SettingsPage.create("toSpp")
+ val owner = createSettingsPage("mySpp")
+ val fromPage = createSettingsPage("fromSpp")
+ val toPage = createSettingsPage("toSpp")
val entryFrom =
SettingsEntryBuilder.createLinkFrom("myEntry", owner).setLink(toPage = toPage).build()
- assertThat(entryFrom.id).isEqualTo(getUniqueEntryId("myEntry", owner, owner, toPage))
+ assertThat(entryFrom.id).isEqualTo(genEntryId("myEntry", owner, owner, toPage))
assertThat(entryFrom.displayName).isEqualTo("myEntry")
assertThat(entryFrom.fromPage!!.sppName).isEqualTo("mySpp")
assertThat(entryFrom.toPage!!.sppName).isEqualTo("toSpp")
val entryTo =
SettingsEntryBuilder.createLinkTo("myEntry", owner).setLink(fromPage = fromPage).build()
- assertThat(entryTo.id).isEqualTo(getUniqueEntryId("myEntry", owner, fromPage, owner))
+ assertThat(entryTo.id).isEqualTo(genEntryId("myEntry", owner, fromPage, owner))
assertThat(entryTo.displayName).isEqualTo("myEntry")
assertThat(entryTo.fromPage!!.sppName).isEqualTo("fromSpp")
assertThat(entryTo.toPage!!.sppName).isEqualTo("mySpp")
@@ -100,10 +101,10 @@
@Test
fun testBuildInject() {
- val owner = SettingsPage.create("mySpp")
+ val owner = createSettingsPage("mySpp")
val entryInject = SettingsEntryBuilder.createInject(owner).build()
assertThat(entryInject.id).isEqualTo(
- getUniqueEntryId(
+ genEntryId(
INJECT_ENTRY_NAME_TEST, owner, toPage = owner
)
)
@@ -114,10 +115,10 @@
@Test
fun testBuildRoot() {
- val owner = SettingsPage.create("mySpp")
+ val owner = createSettingsPage("mySpp")
val entryInject = SettingsEntryBuilder.createRoot(owner, "myRootEntry").build()
assertThat(entryInject.id).isEqualTo(
- getUniqueEntryId(
+ genEntryId(
ROOT_ENTRY_NAME_TEST, owner, toPage = owner
)
)
@@ -129,7 +130,7 @@
@Test
fun testSetAttributes() {
SpaEnvironmentFactory.reset(spaEnvironment)
- val owner = SettingsPage.create("SppHome")
+ val owner = createSettingsPage("SppHome")
val entryBuilder =
SettingsEntryBuilder.create(owner, "myEntry")
.setDisplayName("myEntryDisplay")
@@ -138,7 +139,7 @@
.setSearchDataFn { null }
.setSliceDataFn { _, _ -> null }
val entry = entryBuilder.build()
- assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+ assertThat(entry.id).isEqualTo(genEntryId("myEntry", owner))
assertThat(entry.displayName).isEqualTo("myEntryDisplay")
assertThat(entry.fromPage).isNull()
assertThat(entry.toPage).isNull()
@@ -148,7 +149,7 @@
assertThat(entry.hasSliceSupport).isTrue()
// Test disabled Spp
- val ownerDisabled = SettingsPage.create("SppDisabled")
+ val ownerDisabled = createSettingsPage("SppDisabled")
val entryBuilderDisabled =
SettingsEntryBuilder.create(ownerDisabled, "myEntry")
.setDisplayName("myEntryDisplay")
@@ -157,7 +158,7 @@
.setSearchDataFn { null }
.setSliceDataFn { _, _ -> null }
val entryDisabled = entryBuilderDisabled.build()
- assertThat(entryDisabled.id).isEqualTo(getUniqueEntryId("myEntry", ownerDisabled))
+ assertThat(entryDisabled.id).isEqualTo(genEntryId("myEntry", ownerDisabled))
assertThat(entryDisabled.displayName).isEqualTo("myEntryDisplay")
assertThat(entryDisabled.fromPage).isNull()
assertThat(entryDisabled.toPage).isNull()
@@ -173,7 +174,7 @@
// Clear SppHome in spa environment
SpaEnvironmentFactory.reset()
val entry3 = entryBuilder.build()
- assertThat(entry3.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+ assertThat(entry3.id).isEqualTo(genEntryId("myEntry", owner))
assertThat(entry3.displayName).isEqualTo("myEntryDisplay")
assertThat(entry3.fromPage).isNull()
assertThat(entry3.toPage).isNull()
@@ -186,12 +187,12 @@
@Test
fun testSetMarco() {
SpaEnvironmentFactory.reset(spaEnvironment)
- val owner = SettingsPage.create("SppHome", arguments = bundleOf("param" to "v1"))
+ val owner = createSettingsPage("SppHome", arguments = bundleOf("param" to "v1"))
val entry = SettingsEntryBuilder.create(owner, "myEntry").setMacro {
assertThat(it?.getString("param")).isEqualTo("v1")
assertThat(it?.getString("rtParam")).isEqualTo("v2")
assertThat(it?.getString("unknown")).isNull()
- MacroForTest(getUniquePageId("SppHome"), getUniqueEntryId("myEntry", owner))
+ MacroForTest(genPageId("SppHome"), genEntryId("myEntry", owner))
}.build()
val rtArguments = bundleOf("rtParam" to "v2")
@@ -211,8 +212,8 @@
@Test
fun testSetSliceDataFn() {
SpaEnvironmentFactory.reset(spaEnvironment)
- val owner = SettingsPage.create("SppHome")
- val entryId = getUniqueEntryId("myEntry", owner)
+ val owner = createSettingsPage("SppHome")
+ val entryId = genEntryId("myEntry", owner)
val emptySliceData = EntrySliceData()
val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry").setSliceDataFn { uri, _ ->
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
index 6c0c652..8576573 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spa.framework.common
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.tests.testutils.createSettingsPage
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -29,13 +30,14 @@
assertThat(sppRepoEmpty.getDefaultStartPage()).isEqualTo("")
assertThat(sppRepoEmpty.getAllRootPages()).isEmpty()
+ val nullPage = NullPageProvider.createSettingsPage()
val sppRepoNull =
- SettingsPageProviderRepository(emptyList(), listOf(SettingsPage.createNull()))
+ SettingsPageProviderRepository(emptyList(), listOf(nullPage))
assertThat(sppRepoNull.getDefaultStartPage()).isEqualTo("NULL")
- assertThat(sppRepoNull.getAllRootPages()).contains(SettingsPage.createNull())
+ assertThat(sppRepoNull.getAllRootPages()).contains(nullPage)
- val rootPage1 = SettingsPage.create(name = "Spp1", displayName = "Spp1")
- val rootPage2 = SettingsPage.create(name = "Spp2", displayName = "Spp2")
+ val rootPage1 = createSettingsPage(sppName = "Spp1", displayName = "Spp1")
+ val rootPage2 = createSettingsPage(sppName = "Spp2", displayName = "Spp2")
val sppRepo = SettingsPageProviderRepository(emptyList(), listOf(rootPage1, rootPage2))
val allRoots = sppRepo.getAllRootPages()
assertThat(sppRepo.getDefaultStartPage()).isEqualTo("Spp1")
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
index 7fa1e26..dc74a10 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -22,8 +22,9 @@
import androidx.navigation.navArgument
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.util.genPageId
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
-import com.android.settingslib.spa.tests.testutils.getUniquePageId
+import com.android.settingslib.spa.tests.testutils.createSettingsPage
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,27 +36,25 @@
@Test
fun testNullPage() {
- val page = SettingsPage.createNull()
- assertThat(page.id).isEqualTo(getUniquePageId("NULL"))
+ val page = NullPageProvider.createSettingsPage()
+ assertThat(page.id).isEqualTo(genPageId("NULL"))
assertThat(page.sppName).isEqualTo("NULL")
assertThat(page.displayName).isEqualTo("NULL")
assertThat(page.buildRoute()).isEqualTo("NULL")
assertThat(page.isCreateBy("NULL")).isTrue()
assertThat(page.isCreateBy("Spp")).isFalse()
- assertThat(page.hasRuntimeParam()).isFalse()
assertThat(page.isBrowsable()).isFalse()
}
@Test
fun testRegularPage() {
- val page = SettingsPage.create("mySpp", "SppDisplayName")
- assertThat(page.id).isEqualTo(getUniquePageId("mySpp"))
+ val page = createSettingsPage("mySpp", "SppDisplayName")
+ assertThat(page.id).isEqualTo(genPageId("mySpp"))
assertThat(page.sppName).isEqualTo("mySpp")
assertThat(page.displayName).isEqualTo("SppDisplayName")
assertThat(page.buildRoute()).isEqualTo("mySpp")
assertThat(page.isCreateBy("NULL")).isFalse()
assertThat(page.isCreateBy("mySpp")).isTrue()
- assertThat(page.hasRuntimeParam()).isFalse()
assertThat(page.isBrowsable()).isTrue()
}
@@ -67,7 +66,7 @@
)
val page = spaEnvironment.createPage("SppWithParam", arguments)
assertThat(page.id).isEqualTo(
- getUniquePageId(
+ genPageId(
"SppWithParam", listOf(
navArgument("string_param") { type = NavType.StringType },
navArgument("int_param") { type = NavType.IntType },
@@ -78,7 +77,6 @@
assertThat(page.displayName).isEqualTo("SppWithParam/myStr/10")
assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
assertThat(page.isCreateBy("SppWithParam")).isTrue()
- assertThat(page.hasRuntimeParam()).isFalse()
assertThat(page.isBrowsable()).isTrue()
}
@@ -91,7 +89,7 @@
)
val page = spaEnvironment.createPage("SppWithRtParam", arguments)
assertThat(page.id).isEqualTo(
- getUniquePageId(
+ genPageId(
"SppWithRtParam", listOf(
navArgument("string_param") { type = NavType.StringType },
navArgument("int_param") { type = NavType.IntType },
@@ -103,7 +101,6 @@
assertThat(page.displayName).isEqualTo("SppWithRtParam/myStr/10")
assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
assertThat(page.isCreateBy("SppWithRtParam")).isTrue()
- assertThat(page.hasRuntimeParam()).isTrue()
assertThat(page.isBrowsable()).isFalse()
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
index 1854728..f974cca 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
@@ -19,9 +19,10 @@
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.NullPageProvider
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.google.common.truth.Truth
import org.junit.Before
@@ -40,7 +41,7 @@
@Test
fun testCreateIntent() {
- val nullPage = SettingsPage.createNull()
+ val nullPage = NullPageProvider.createSettingsPage()
Truth.assertThat(nullPage.createIntent()).isNull()
Truth.assertThat(SettingsEntryBuilder.createInject(nullPage).build().createIntent())
.isNull()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/UniqueIdTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/UniqueIdTest.kt
new file mode 100644
index 0000000..c17f83b
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/UniqueIdTest.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.settingslib.spa.framework.util
+
+import androidx.core.os.bundleOf
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.tests.testutils.createSettingsPage
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class UniqueIdTest {
+ @Test
+ fun testUniquePageId() {
+ Truth.assertThat(genPageId("mySpp")).isEqualTo("1byojwa")
+
+ val parameter = listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ )
+ val arguments = bundleOf(
+ "string_param" to "myStr",
+ "int_param" to 10,
+ )
+ Truth.assertThat(genPageId("mySppWithParam", parameter, arguments)).isEqualTo("1sz4pbq")
+
+ val parameter2 = listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ navArgument("rt_param") { type = NavType.StringType },
+ )
+ val arguments2 = bundleOf(
+ "string_param" to "myStr",
+ "int_param" to 10,
+ "rt_param" to "myRtStr",
+ )
+ Truth.assertThat(genPageId("mySppWithRtParam", parameter2, arguments2)).isEqualTo("ts6d8k")
+ }
+
+ @Test
+ fun testUniqueEntryId() {
+ val owner = createSettingsPage("mySpp")
+ val fromPage = createSettingsPage("fromSpp")
+ val toPage = createSettingsPage("toSpp")
+
+ Truth.assertThat(genEntryId("myEntry", owner)).isEqualTo("145pppn")
+ Truth.assertThat(genEntryId("myEntry", owner, fromPage = fromPage, toPage = toPage))
+ .isEqualTo("1m7jzew")
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
index 530d2ed..341a4a5 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
@@ -25,10 +25,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.util.genEntryId
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.tests.testutils.SppLayer2
-import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -52,7 +52,7 @@
// Slice supported
val page = SppLayer2.createSettingsPage()
- val entryId = getUniqueEntryId("Layer2Entry1", page)
+ val entryId = genEntryId("Layer2Entry1", page)
val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2")
assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
@@ -61,7 +61,7 @@
assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri)).isSameInstanceAs(sliceData)
// Slice unsupported
- val entryId2 = getUniqueEntryId("Layer2Entry2", page)
+ val entryId2 = genEntryId("Layer2Entry2", page)
val sliceUri2 = Uri.Builder().appendSpaParams(page.buildRoute(), entryId2).build()
assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2")
assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]")
@@ -73,7 +73,7 @@
SpaEnvironmentFactory.reset(spaEnvironment)
val page = SppLayer2.createSettingsPage()
- val entryId = getUniqueEntryId("Layer2Entry1", page)
+ val entryId = genEntryId("Layer2Entry1", page)
val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
// build slice data first
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SettingsPageHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SettingsPageHelper.kt
new file mode 100644
index 0000000..caf4136
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SettingsPageHelper.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.spa.tests.testutils
+
+import android.os.Bundle
+import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.util.genPageId
+
+fun createSettingsPage(
+ sppName: String,
+ displayName: String? = null,
+ parameter: List<NamedNavArgument> = emptyList(),
+ arguments: Bundle? = null
+): SettingsPage {
+ return SettingsPage(
+ id = genPageId(sppName, parameter, arguments),
+ sppName = sppName,
+ displayName = displayName ?: sppName,
+ parameter = parameter,
+ arguments = arguments
+ )
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
deleted file mode 100644
index ce9b791..0000000
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
+++ /dev/null
@@ -1,48 +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.settingslib.spa.tests.testutils
-
-import android.os.Bundle
-import androidx.navigation.NamedNavArgument
-import com.android.settingslib.spa.framework.common.SettingsPage
-import com.android.settingslib.spa.framework.common.toHashId
-import com.android.settingslib.spa.framework.util.normalize
-
-fun getUniquePageId(
- name: String,
- parameter: List<NamedNavArgument> = emptyList(),
- arguments: Bundle? = null
-): String {
- val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
- return "$name:${normArguments?.toString()}".toHashId()
-}
-
-fun getUniquePageId(page: SettingsPage): String {
- return getUniquePageId(page.sppName, page.parameter, page.arguments)
-}
-
-fun getUniqueEntryId(
- name: String,
- owner: SettingsPage,
- fromPage: SettingsPage? = null,
- toPage: SettingsPage? = null
-): String {
- val ownerId = getUniquePageId(owner)
- val fromId = if (fromPage == null) "null" else getUniquePageId(fromPage)
- val toId = if (toPage == null) "null" else getUniquePageId(toPage)
- return "$name:$ownerId($fromId-$toId)".toHashId()
-}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index c155433..adc6a02 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1633,4 +1633,12 @@
<string name="back_navigation_animation_summary">Enable system animations for predictive back.</string>
<!-- Developer setting: enable animations when a back gesture is executed, full explanation[CHAR LIMIT=NONE] -->
<string name="back_navigation_animation_dialog">This setting enables system animations for predictive gesture animation. It requires setting per-app "enableOnBackInvokedCallback" to true in the manifest file.</string>
+
+ <!-- [CHAR LIMIT=NONE] Messages shown when users press outside of udfps region during -->
+ <string-array name="udfps_accessibility_touch_hints">
+ <item>Move left</item>
+ <item>Move down</item>
+ <item>Move right</item>
+ <item>Move up</item>
+ </string-array>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/OWNERS b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/OWNERS
new file mode 100644
index 0000000..09e7991
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/OWNERS
@@ -0,0 +1,5 @@
+# Default reviewers for this and subdirectories.
+emilychuang@google.com
+ykhung@google.com
+
+# Emergency approvers in case the above are not available
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt b/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsOverlayParams.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt
rename to packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsOverlayParams.kt
index 7f3846c..d55a027 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsOverlayParams.kt
@@ -1,4 +1,4 @@
-package com.android.systemui.biometrics
+package com.android.settingslib.udfps
import android.graphics.Rect
import android.view.Surface
diff --git a/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsUtils.java b/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsUtils.java
new file mode 100644
index 0000000..dc8a862
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsUtils.java
@@ -0,0 +1,159 @@
+/*
+ * 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.udfps;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.util.DisplayUtils;
+import android.util.Log;
+import android.util.RotationUtils;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.MotionEvent;
+import android.view.Surface;
+
+import com.android.settingslib.R;
+
+/** Utility class for working with udfps. */
+public class UdfpsUtils {
+ private static final String TAG = "UdfpsUtils";
+
+ /**
+ * Gets the scale factor representing the user's current resolution / the stable (default)
+ * resolution.
+ *
+ * @param displayInfo The display information.
+ */
+ public float getScaleFactor(DisplayInfo displayInfo) {
+ Display.Mode maxDisplayMode =
+ DisplayUtils.getMaximumResolutionDisplayMode(displayInfo.supportedModes);
+ float scaleFactor =
+ DisplayUtils.getPhysicalPixelDisplaySizeRatio(
+ maxDisplayMode.getPhysicalWidth(),
+ maxDisplayMode.getPhysicalHeight(),
+ displayInfo.getNaturalWidth(),
+ displayInfo.getNaturalHeight()
+ );
+ return (scaleFactor == Float.POSITIVE_INFINITY) ? 1f : scaleFactor;
+ }
+
+ /**
+ * Gets the touch in native coordinates. Map the touch to portrait mode if the device is in
+ * landscape mode.
+ *
+ * @param idx The pointer identifier.
+ * @param event The MotionEvent object containing full information about the event.
+ * @param udfpsOverlayParams The [UdfpsOverlayParams] used.
+ * @return The mapped touch event.
+ */
+ public Point getTouchInNativeCoordinates(int idx, MotionEvent event,
+ UdfpsOverlayParams udfpsOverlayParams) {
+ Point portraitTouch = new Point((int) event.getRawX(idx), (int) event.getRawY(idx));
+ int rot = udfpsOverlayParams.getRotation();
+ if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
+ RotationUtils.rotatePoint(
+ portraitTouch,
+ RotationUtils.deltaRotation(rot, Surface.ROTATION_0),
+ udfpsOverlayParams.getLogicalDisplayWidth(),
+ udfpsOverlayParams.getLogicalDisplayHeight()
+ );
+ }
+
+ // Scale the coordinates to native resolution.
+ float scale = udfpsOverlayParams.getScaleFactor();
+ portraitTouch.x = (int) (portraitTouch.x / scale);
+ portraitTouch.y = (int) (portraitTouch.y / scale);
+ return portraitTouch;
+ }
+
+ /**
+ * This function computes the angle of touch relative to the sensor and maps the angle to a list
+ * of help messages which are announced if accessibility is enabled.
+ *
+ * @return Whether the announcing string is null
+ */
+ public String onTouchOutsideOfSensorArea(boolean touchExplorationEnabled,
+ Context context, int touchX, int touchY, UdfpsOverlayParams udfpsOverlayParams) {
+ if (!touchExplorationEnabled) {
+ return null;
+ }
+
+ String[] touchHints = context.getResources().getStringArray(
+ R.array.udfps_accessibility_touch_hints);
+ if (touchHints.length != 4) {
+ Log.e(TAG, "expected exactly 4 touch hints, got " + touchHints.length + "?");
+ return null;
+ }
+
+ // Scale the coordinates to native resolution.
+ float scale = udfpsOverlayParams.getScaleFactor();
+ float scaledSensorX = udfpsOverlayParams.getSensorBounds().centerX() / scale;
+ float scaledSensorY = udfpsOverlayParams.getSensorBounds().centerY() / scale;
+ String theStr =
+ onTouchOutsideOfSensorAreaImpl(
+ touchHints,
+ touchX,
+ touchY,
+ scaledSensorX,
+ scaledSensorY,
+ udfpsOverlayParams.getRotation()
+ );
+ Log.v(TAG, "Announcing touch outside : $theStr");
+ return theStr;
+ }
+
+ /**
+ * This function computes the angle of touch relative to the sensor and maps the angle to a list
+ * of help messages which are announced if accessibility is enabled.
+ *
+ * There are 4 quadrants of the circle (90 degree arcs)
+ *
+ * [315, 360] && [0, 45) -> touchHints[0] = "Move Fingerprint to the left" [45, 135) ->
+ * touchHints[1] = "Move Fingerprint down" And so on.
+ */
+ private String onTouchOutsideOfSensorAreaImpl(String[] touchHints, float touchX,
+ float touchY, float sensorX, float sensorY, int rotation) {
+ float xRelativeToSensor = touchX - sensorX;
+ // Touch coordinates are with respect to the upper left corner, so reverse
+ // this calculation
+ float yRelativeToSensor = sensorY - touchY;
+ double angleInRad = Math.atan2(yRelativeToSensor, xRelativeToSensor);
+ // If the radians are negative, that means we are counting clockwise.
+ // So we need to add 360 degrees
+ if (angleInRad < 0.0) {
+ angleInRad += 2.0 * Math.PI;
+ }
+ // rad to deg conversion
+ double degrees = Math.toDegrees(angleInRad);
+ double degreesPerBucket = 360.0 / touchHints.length;
+ double halfBucketDegrees = degreesPerBucket / 2.0;
+ // The mapping should be as follows
+ // [315, 360] && [0, 45] -> 0
+ // [45, 135] -> 1
+ int index = (int) ((degrees + halfBucketDegrees) % 360 / degreesPerBucket);
+ index %= touchHints.length;
+
+ // A rotation of 90 degrees corresponds to increasing the index by 1.
+ if (rotation == Surface.ROTATION_90) {
+ index = (index + 1) % touchHints.length;
+ }
+ if (rotation == Surface.ROTATION_270) {
+ index = (index + 3) % touchHints.length;
+ }
+ return touchHints[index];
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/udfps/UdfpsUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/udfps/UdfpsUtilsTest.java
new file mode 100644
index 0000000..f4f0ef9
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/udfps/UdfpsUtilsTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.udfps;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.Surface;
+
+import com.android.settingslib.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+
+@RunWith(RobolectricTestRunner.class)
+public class UdfpsUtilsTest {
+ @Rule
+ public final MockitoRule rule = MockitoJUnit.rule();
+
+ private Context mContext;
+ private String[] mTouchHints;
+ private UdfpsUtils mUdfpsUtils;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mTouchHints = mContext.getResources().getStringArray(
+ R.array.udfps_accessibility_touch_hints);
+ mUdfpsUtils = new UdfpsUtils();
+ }
+
+ @Test
+ public void testTouchOutsideAreaNoRotation() {
+ int rotation = Surface.ROTATION_0;
+ // touch at 0 degrees
+ assertThat(
+ mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
+ 0 /* touchX */, 0/* touchY */,
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ )
+ ).isEqualTo(mTouchHints[0]);
+ // touch at 90 degrees
+ assertThat(
+ mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
+ 0 /* touchX */, -1/* touchY */,
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ )
+ ).isEqualTo(mTouchHints[1]);
+ // touch at 180 degrees
+ assertThat(
+ mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
+ -1 /* touchX */, 0/* touchY */,
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ )
+ ).isEqualTo(mTouchHints[2]);
+ // touch at 270 degrees
+ assertThat(
+ mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
+ 0 /* touchX */, 1/* touchY */,
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ )
+ ).isEqualTo(mTouchHints[3]);
+ }
+
+
+ @Test
+ public void testTouchOutsideAreaNoRotation90Degrees() {
+ int rotation = Surface.ROTATION_90;
+ // touch at 0 degrees -> 90 degrees
+ assertThat(
+ mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
+ 0 /* touchX */, 0 /* touchY */,
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ )
+ ).isEqualTo(mTouchHints[1]);
+ // touch at 90 degrees -> 180 degrees
+ assertThat(
+ mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
+ 0 /* touchX */, -1 /* touchY */,
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ )
+ ).isEqualTo(mTouchHints[2]);
+ // touch at 180 degrees -> 270 degrees
+ assertThat(
+ mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
+ -1 /* touchX */, 0 /* touchY */,
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ )
+ ).isEqualTo(mTouchHints[3]);
+ // touch at 270 degrees -> 0 degrees
+ assertThat(
+ mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
+ 0 /* touchX */, 1/* touchY */,
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ )
+ ).isEqualTo(mTouchHints[0]);
+ }
+
+
+ @Test
+ public void testTouchOutsideAreaNoRotation270Degrees() {
+ int rotation = Surface.ROTATION_270;
+ // touch at 0 degrees -> 270 degrees
+ assertThat(
+ mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
+ 0 /* touchX */, 0/* touchY */,
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ )
+ ).isEqualTo(mTouchHints[3]);
+ // touch at 90 degrees -> 0 degrees
+ assertThat(
+ mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
+ 0 /* touchX */, -1/* touchY */,
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ )
+ ).isEqualTo(mTouchHints[0]);
+ // touch at 180 degrees -> 90 degrees
+ assertThat(
+ mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
+ -1 /* touchX */, 0/* touchY */,
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ )
+ ).isEqualTo(mTouchHints[1]);
+ // touch at 270 degrees -> 180 degrees
+ assertThat(
+ mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
+ 0 /* touchX */, 1/* touchY */,
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ )
+ ).isEqualTo(mTouchHints[2]);
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index c764d53..74bc910 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -87,7 +87,7 @@
* (1.-sparkleRing) * in_fadeSparkle;
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
- float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing * in_sparkle_strength;
+ float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index bc4796a..3c9328c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -117,15 +117,6 @@
rippleShader.color = ColorUtils.setAlphaComponent(color, alpha)
}
- /**
- * Set whether the ripple should remain filled as the ripple expands.
- *
- * See [RippleShader.rippleFill].
- */
- fun setRippleFill(rippleFill: Boolean) {
- rippleShader.rippleFill = rippleFill
- }
-
/** Set the intensity of the sparkles. */
fun setSparkleStrength(strength: Float) {
rippleShader.sparkleStrength = strength
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 bbfb6fb..4ef525a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -20,6 +20,7 @@
import com.android.internal.annotations.Keep
import com.android.systemui.plugins.annotations.ProvidesInterface
import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.statusbar.Weather
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
@@ -111,6 +112,9 @@
/** Call whenever the color palette should update */
fun onColorPaletteChanged(resources: Resources) {}
+
+ /** Call whenever the weather data should update */
+ fun onWeatherDataChanged(data: Weather) {}
}
/** Methods which trigger various clock animations */
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index 64ece47..ca4028a 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -105,6 +105,7 @@
android:id="@+id/key1"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key2"
androidprv:digit="1"
androidprv:textView="@+id/pinEntry" />
@@ -112,6 +113,7 @@
android:id="@+id/key2"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key3"
androidprv:digit="2"
androidprv:textView="@+id/pinEntry" />
@@ -119,6 +121,7 @@
android:id="@+id/key3"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key4"
androidprv:digit="3"
androidprv:textView="@+id/pinEntry" />
@@ -126,6 +129,7 @@
android:id="@+id/key4"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key5"
androidprv:digit="4"
androidprv:textView="@+id/pinEntry" />
@@ -133,6 +137,7 @@
android:id="@+id/key5"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key6"
androidprv:digit="5"
androidprv:textView="@+id/pinEntry" />
@@ -140,6 +145,7 @@
android:id="@+id/key6"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key7"
androidprv:digit="6"
androidprv:textView="@+id/pinEntry" />
@@ -147,13 +153,16 @@
android:id="@+id/key7"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key8"
androidprv:digit="7"
androidprv:textView="@+id/pinEntry" />
+
<com.android.keyguard.NumPadKey
android:id="@+id/key8"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key9"
androidprv:digit="8"
androidprv:textView="@+id/pinEntry" />
@@ -161,34 +170,33 @@
android:id="@+id/key9"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/delete_button"
androidprv:digit="9"
androidprv:textView="@+id/pinEntry" />
-
<com.android.keyguard.NumPadButton
android:id="@+id/delete_button"
+ style="@style/NumPadKey.Delete"
android:layout_width="0dp"
android:layout_height="0dp"
- style="@style/NumPadKey.Delete"
- android:contentDescription="@string/keyboardview_keycode_delete"
- />
+ android:accessibilityTraversalBefore="@id/key0"
+ android:contentDescription="@string/keyboardview_keycode_delete" />
<com.android.keyguard.NumPadKey
android:id="@+id/key0"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key_enter"
androidprv:digit="0"
androidprv:textView="@+id/pinEntry" />
<com.android.keyguard.NumPadButton
android:id="@+id/key_enter"
+ style="@style/NumPadKey.Enter"
android:layout_width="0dp"
android:layout_height="0dp"
- style="@style/NumPadKey.Enter"
- android:contentDescription="@string/keyboardview_keycode_enter"
- />
-
- </androidx.constraintlayout.widget.ConstraintLayout>
+ android:contentDescription="@string/keyboardview_keycode_enter" />
+</androidx.constraintlayout.widget.ConstraintLayout>
<include layout="@layout/keyguard_eca"
android:id="@+id/keyguard_selector_fade_container"
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index ded6f93..aa211bf 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -20,7 +20,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:layout_marginTop="@dimen/controls_top_margin"
android:layout_marginBottom="@dimen/controls_header_bottom_margin">
<!-- make sure the header stays centered in the layout by adding a spacer -->
@@ -78,6 +77,7 @@
android:layout_weight="1"
android:orientation="vertical"
android:clipChildren="true"
+ android:paddingHorizontal="16dp"
android:scrollbars="none">
<include layout="@layout/global_actions_controls_list_view" />
@@ -88,8 +88,6 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:layout_marginLeft="@dimen/global_actions_side_margin"
- android:layout_marginRight="@dimen/global_actions_side_margin"
android:background="@drawable/controls_panel_background"
android:visibility="gone"
/>
diff --git a/packages/SystemUI/res/layout/udfps_fpm_empty_view.xml b/packages/SystemUI/res/layout/udfps_fpm_empty_view.xml
index de43a5e..4799f8c 100644
--- a/packages/SystemUI/res/layout/udfps_fpm_empty_view.xml
+++ b/packages/SystemUI/res/layout/udfps_fpm_empty_view.xml
@@ -19,4 +19,12 @@
android:id="@+id/udfps_animation_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <!-- The layout height/width are placeholders, which will be overwritten by
+ FingerprintSensorPropertiesInternal. -->
+ <View
+ android:id="@+id/udfps_enroll_accessibility_view"
+ android:layout_gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:contentDescription="@string/accessibility_fingerprint_label"/>
</com.android.systemui.biometrics.UdfpsFpmEmptyView>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index fff2544..ac81dcc 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -64,4 +64,6 @@
<dimen name="qs_panel_padding_top">@dimen/qqs_layout_margin_top</dimen>
<dimen name="qs_panel_padding_top_combined_headers">@dimen/qs_panel_padding_top</dimen>
+
+ <dimen name="controls_padding_horizontal">16dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index db7fb48..4f24d83 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -51,9 +51,6 @@
<!-- Text size for user name in user switcher -->
<dimen name="kg_user_switcher_text_size">18sp</dimen>
- <dimen name="controls_header_bottom_margin">12dp</dimen>
- <dimen name="controls_top_margin">24dp</dimen>
-
<dimen name="global_actions_grid_item_layout_height">80dp</dimen>
<dimen name="qs_brightness_margin_bottom">16dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 122806a..2b88e55 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -17,7 +17,6 @@
*/
-->
<resources>
- <dimen name="controls_padding_horizontal">205dp</dimen>
<dimen name="split_shade_notifications_scrim_margin_bottom">24dp</dimen>
<dimen name="notification_panel_margin_bottom">64dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 927059a..8f59df6 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -19,7 +19,7 @@
<!-- gap on either side of status bar notification icons -->
<dimen name="status_bar_icon_padding">1dp</dimen>
- <dimen name="controls_padding_horizontal">75dp</dimen>
+ <dimen name="controls_padding_horizontal">40dp</dimen>
<dimen name="large_screen_shade_header_height">56dp</dimen>
diff --git a/packages/SystemUI/res/values-television/strings.xml b/packages/SystemUI/res/values-television/strings.xml
deleted file mode 100644
index 86106e6..0000000
--- a/packages/SystemUI/res/values-television/strings.xml
+++ /dev/null
@@ -1,22 +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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
- <string name="log_access_confirmation_learn_more_url" translatable="false"></string>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ebf232f..2df2513 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -43,12 +43,18 @@
<dimen name="navigation_edge_panel_height">268dp</dimen>
<!-- The threshold to drag to trigger the edge action -->
<dimen name="navigation_edge_action_drag_threshold">16dp</dimen>
+ <!-- The drag distance to consider evaluating gesture -->
+ <dimen name="navigation_edge_action_min_distance_to_start_animation">24dp</dimen>
<!-- The threshold to progress back animation for edge swipe -->
<dimen name="navigation_edge_action_progress_threshold">412dp</dimen>
<!-- The minimum display position of the arrow on the screen -->
<dimen name="navigation_edge_arrow_min_y">64dp</dimen>
<!-- The amount by which the arrow is shifted to avoid the finger-->
<dimen name="navigation_edge_finger_offset">64dp</dimen>
+ <!-- The threshold to dynamically activate the edge action -->
+ <dimen name="navigation_edge_action_reactivation_drag_threshold">32dp</dimen>
+ <!-- The threshold to dynamically deactivate the edge action -->
+ <dimen name="navigation_edge_action_deactivation_drag_threshold">32dp</dimen>
<!-- The thickness of the arrow -->
<dimen name="navigation_edge_arrow_thickness">4dp</dimen>
@@ -56,37 +62,61 @@
<dimen name="navigation_edge_minimum_x_delta_for_switch">32dp</dimen>
<!-- entry state -->
+ <item name="navigation_edge_entry_scale" format="float" type="dimen">0.98</item>
<dimen name="navigation_edge_entry_margin">4dp</dimen>
- <dimen name="navigation_edge_entry_background_width">8dp</dimen>
- <dimen name="navigation_edge_entry_background_height">60dp</dimen>
- <dimen name="navigation_edge_entry_edge_corners">30dp</dimen>
- <dimen name="navigation_edge_entry_far_corners">30dp</dimen>
- <dimen name="navigation_edge_entry_arrow_length">10dp</dimen>
- <dimen name="navigation_edge_entry_arrow_height">7dp</dimen>
+ <item name="navigation_edge_entry_background_alpha" format="float" type="dimen">1.0</item>
+ <dimen name="navigation_edge_entry_background_width">0dp</dimen>
+ <dimen name="navigation_edge_entry_background_height">48dp</dimen>
+ <dimen name="navigation_edge_entry_edge_corners">6dp</dimen>
+ <dimen name="navigation_edge_entry_far_corners">6dp</dimen>
+ <item name="navigation_edge_entry_arrow_alpha" format="float" type="dimen">0.0</item>
+ <dimen name="navigation_edge_entry_arrow_length">8.6dp</dimen>
+ <dimen name="navigation_edge_entry_arrow_height">5dp</dimen>
<!-- pre-threshold -->
<dimen name="navigation_edge_pre_threshold_margin">4dp</dimen>
- <dimen name="navigation_edge_pre_threshold_background_width">64dp</dimen>
- <dimen name="navigation_edge_pre_threshold_background_height">60dp</dimen>
- <dimen name="navigation_edge_pre_threshold_edge_corners">22dp</dimen>
- <dimen name="navigation_edge_pre_threshold_far_corners">26dp</dimen>
+ <item name="navigation_edge_pre_threshold_background_alpha" format="float" type="dimen">1.0
+ </item>
+ <item name="navigation_edge_pre_threshold_scale" format="float" type="dimen">0.98</item>
+ <dimen name="navigation_edge_pre_threshold_background_width">51dp</dimen>
+ <dimen name="navigation_edge_pre_threshold_background_height">46dp</dimen>
+ <dimen name="navigation_edge_pre_threshold_edge_corners">16dp</dimen>
+ <dimen name="navigation_edge_pre_threshold_far_corners">20dp</dimen>
+ <item name="navigation_edge_pre_threshold_arrow_alpha" format="float" type="dimen">1.0</item>
+ <dimen name="navigation_edge_pre_threshold_arrow_length">8dp</dimen>
+ <dimen name="navigation_edge_pre_threshold_arrow_height">5.6dp</dimen>
- <!-- post-threshold / active -->
+ <!-- active (post-threshold) -->
+ <item name="navigation_edge_active_scale" format="float" type="dimen">1.0</item>
<dimen name="navigation_edge_active_margin">14dp</dimen>
- <dimen name="navigation_edge_active_background_width">60dp</dimen>
- <dimen name="navigation_edge_active_background_height">60dp</dimen>
- <dimen name="navigation_edge_active_edge_corners">30dp</dimen>
- <dimen name="navigation_edge_active_far_corners">30dp</dimen>
- <dimen name="navigation_edge_active_arrow_length">8dp</dimen>
- <dimen name="navigation_edge_active_arrow_height">9dp</dimen>
+ <item name="navigation_edge_active_background_alpha" format="float" type="dimen">1.0</item>
+ <dimen name="navigation_edge_active_background_width">48dp</dimen>
+ <dimen name="navigation_edge_active_background_height">48dp</dimen>
+ <dimen name="navigation_edge_active_edge_corners">24dp</dimen>
+ <dimen name="navigation_edge_active_far_corners">24dp</dimen>
+ <item name="navigation_edge_active_arrow_alpha" format="float" type="dimen">1.0</item>
+ <dimen name="navigation_edge_active_arrow_length">6.4dp</dimen>
+ <dimen name="navigation_edge_active_arrow_height">7.2dp</dimen>
+ <!-- committed -->
+ <item name="navigation_edge_committed_scale" format="float" type="dimen">0.85</item>
+ <item name="navigation_edge_committed_alpha" format="float" type="dimen">0</item>
+
+ <!-- cancelled -->
+ <dimen name="navigation_edge_cancelled_background_width">0dp</dimen>
+
+ <item name="navigation_edge_stretch_scale" format="float" type="dimen">1.0</item>
<dimen name="navigation_edge_stretch_margin">18dp</dimen>
- <dimen name="navigation_edge_stretch_background_width">74dp</dimen>
- <dimen name="navigation_edge_stretch_background_height">60dp</dimen>
- <dimen name="navigation_edge_stretch_edge_corners">30dp</dimen>
- <dimen name="navigation_edge_stretch_far_corners">30dp</dimen>
- <dimen name="navigation_edge_stretched_arrow_length">7dp</dimen>
- <dimen name="navigation_edge_stretched_arrow_height">10dp</dimen>
+ <dimen name="navigation_edge_stretch_background_width">60dp</dimen>
+ <item name="navigation_edge_stretch_background_alpha" format="float" type="dimen">
+ @dimen/navigation_edge_entry_background_alpha
+ </item>
+ <dimen name="navigation_edge_stretch_background_height">48dp</dimen>
+ <dimen name="navigation_edge_stretch_edge_corners">24dp</dimen>
+ <dimen name="navigation_edge_stretch_far_corners">24dp</dimen>
+ <item name="navigation_edge_strech_arrow_alpha" format="float" type="dimen">1.0</item>
+ <dimen name="navigation_edge_stretched_arrow_length">5.6dp</dimen>
+ <dimen name="navigation_edge_stretched_arrow_height">8dp</dimen>
<dimen name="navigation_edge_cancelled_arrow_length">12dp</dimen>
<dimen name="navigation_edge_cancelled_arrow_height">0dp</dimen>
@@ -1156,7 +1186,7 @@
<!-- Home Controls -->
<dimen name="controls_header_menu_size">48dp</dimen>
- <dimen name="controls_header_bottom_margin">24dp</dimen>
+ <dimen name="controls_header_bottom_margin">16dp</dimen>
<dimen name="controls_header_app_icon_size">24dp</dimen>
<dimen name="controls_top_margin">48dp</dimen>
<dimen name="controls_padding_horizontal">0dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b535e60..8d95164 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -910,14 +910,6 @@
<!-- Message shown when non-bypass face authentication succeeds. [CHAR LIMIT=60] -->
<string name="keyguard_face_successful_unlock_alt1">Face recognized</string>
- <!-- Messages shown when users press outside of udfps region during -->
- <string-array name="udfps_accessibility_touch_hints">
- <item>Move left</item>
- <item>Move down</item>
- <item>Move right</item>
- <item>Move up</item>
- </string-array>
-
<!-- Message shown when face authentication fails and the pin pad is visible. [CHAR LIMIT=60] -->
<string name="keyguard_retry">Swipe up to try again</string>
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index e56e5d5..00a0444 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -48,8 +48,7 @@
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/clock"
app:layout_constraintEnd_toStartOf="@id/barrier"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBaseline_toBaselineOf="@id/clock"
app:layout_constraintHorizontal_bias="0"
/>
</Constraint>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index b7e2494..44c0e16 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -88,6 +88,7 @@
final ArrayList<WindowContainerToken> pausingTasks = new ArrayList<>();
WindowContainerToken pipTask = null;
WindowContainerToken recentsTask = null;
+ int recentsTaskId = -1;
for (int i = apps.length - 1; i >= 0; --i) {
final ActivityManager.RunningTaskInfo taskInfo = apps[i].taskInfo;
if (apps[i].mode == MODE_CLOSING) {
@@ -106,8 +107,10 @@
// This task is for recents, keep it on top.
t.setLayer(apps[i].leash, info.getChanges().size() * 3 - i);
recentsTask = taskInfo.token;
+ recentsTaskId = taskInfo.taskId;
} else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
recentsTask = taskInfo.token;
+ recentsTaskId = taskInfo.taskId;
}
}
// Also make all the wallpapers opaque since we want the visible from the start
@@ -116,7 +119,7 @@
}
t.apply();
mRecentsSession.setup(controller, info, finishedCallback, pausingTasks, pipTask,
- recentsTask, leashMap, mToken,
+ recentsTask, recentsTaskId, leashMap, mToken,
(info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0);
recents.onAnimationStart(mRecentsSession, apps, wallpapers, new Rect(0, 0, 0, 0),
new Rect());
@@ -154,6 +157,7 @@
private ArrayList<WindowContainerToken> mPausingTasks = null;
private WindowContainerToken mPipTask = null;
private WindowContainerToken mRecentsTask = null;
+ private int mRecentsTaskId = 0;
private TransitionInfo mInfo = null;
private ArrayList<SurfaceControl> mOpeningLeashes = null;
private boolean mOpeningHome = false;
@@ -167,8 +171,8 @@
void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
IRemoteTransitionFinishedCallback finishCB,
ArrayList<WindowContainerToken> pausingTasks, WindowContainerToken pipTask,
- WindowContainerToken recentsTask, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
- IBinder transition, boolean keyguardLocked) {
+ WindowContainerToken recentsTask, int recentsTaskId, ArrayMap<SurfaceControl,
+ SurfaceControl> leashMap, IBinder transition, boolean keyguardLocked) {
if (mInfo != null) {
throw new IllegalStateException("Trying to run a new recents animation while"
+ " recents is already active.");
@@ -179,6 +183,7 @@
mPausingTasks = pausingTasks;
mPipTask = pipTask;
mRecentsTask = recentsTask;
+ mRecentsTaskId = recentsTaskId;
mLeashMap = leashMap;
mTransition = transition;
mKeyguardLocked = keyguardLocked;
@@ -296,6 +301,15 @@
}
@Override public void setInputConsumerEnabled(boolean enabled) {
+ if (enabled) {
+ // transient launches don't receive focus automatically. Since we are taking over
+ // the gesture now, take focus explicitly.
+ try {
+ ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to set focused task", e);
+ }
+ }
if (mWrapped != null) mWrapped.setInputConsumerEnabled(enabled);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 7f95ca6..ea079a9 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -48,6 +48,7 @@
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.shared.regionsampling.RegionSampler
+import com.android.systemui.statusbar.Weather
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -273,6 +274,12 @@
override fun onUserSwitchComplete(userId: Int) {
clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
}
+
+ override fun onWeatherDataChanged(data: Weather?) {
+ if (data != null) {
+ clock?.events?.onWeatherDataChanged(data)
+ }
+ }
}
fun registerListeners(parent: View) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 8011efd..92e3641 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -131,6 +131,7 @@
@Override
void resetState() {
+ mMessageAreaController.setMessage(getInitialMessageResId());
mView.setPasswordEntryEnabled(true);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 57bfe54..c785ee9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -247,6 +247,7 @@
@Override
public void onThemeChanged() {
reloadColors();
+ reset();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index afa9ef6..d23ea9e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -109,7 +109,6 @@
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -154,6 +153,7 @@
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.Weather;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.telephony.TelephonyListenerManager;
@@ -3219,6 +3219,24 @@
}
/**
+ * @param data the weather data (temp, conditions, unit) for weather clock to use
+ */
+ public void sendWeatherData(Weather data) {
+ mHandler.post(()-> {
+ handleWeatherDataUpdate(data); });
+ }
+
+ private void handleWeatherDataUpdate(Weather data) {
+ Assert.isMainThread();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onWeatherDataChanged(data);
+ }
+ }
+ }
+
+ /**
* Handle {@link #MSG_BATTERY_UPDATE}
*/
private void handleBatteryUpdate(BatteryStatus status) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index e6b9ac8..4a7dd24 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -24,6 +24,7 @@
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.Weather;
import java.util.TimeZone;
@@ -58,6 +59,11 @@
public void onTimeFormatChanged(String timeFormat) { }
/**
+ * Called when receive new weather data.
+ */
+ public void onWeatherDataChanged(Weather data) { }
+
+ /**
* Called when the carrier PLMN or SPN changes.
*/
public void onRefreshCarrierInfo() { }
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index c1c7f2d..fb65588 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -124,16 +124,7 @@
};
private final ScreenDecorationsLogger mLogger;
- private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
- @Override
- public void onFaceSensorLocationChanged() {
- mLogger.onSensorLocationChanged();
- if (mExecutor != null) {
- mExecutor.execute(
- () -> updateOverlayProviderViews(new Integer[]{mFaceScanningViewId}));
- }
- }
- };
+ private final AuthController mAuthController;
private DisplayTracker mDisplayTracker;
@VisibleForTesting
@@ -340,9 +331,22 @@
mFaceScanningFactory = faceScanningFactory;
mFaceScanningViewId = com.android.systemui.R.id.face_scanning_anim;
mLogger = logger;
- authController.addCallback(mAuthControllerCallback);
+ mAuthController = authController;
}
+
+ private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+ @Override
+ public void onFaceSensorLocationChanged() {
+ mLogger.onSensorLocationChanged();
+ if (mExecutor != null) {
+ mExecutor.execute(
+ () -> updateOverlayProviderViews(
+ new Integer[]{mFaceScanningViewId}));
+ }
+ }
+ };
+
@Override
public void start() {
if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
@@ -353,6 +357,7 @@
mExecutor = mThreadFactory.buildDelayableExecutorOnHandler(mHandler);
mExecutor.execute(this::startOnScreenDecorationsThread);
mDotViewController.setUiExecutor(mExecutor);
+ mAuthController.addCallback(mAuthControllerCallback);
}
private boolean isPrivacyDotEnabled() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 4c1a9fa..b342a29 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -50,6 +50,7 @@
import android.widget.SeekBar;
import android.widget.Switch;
+import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.R;
@@ -139,8 +140,10 @@
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
float scale = progress * A11Y_CHANGE_SCALE_DIFFERENCE + A11Y_SCALE_MIN_VALUE;
- // update persisted scale only when scale >= 2.0
- if (scale >= 2.0f) {
+ // Update persisted scale only when scale >= PERSISTED_SCALE_MIN_VALUE const.
+ // We assume if the scale is lower than the PERSISTED_SCALE_MIN_VALUE, there will be
+ // no obvious magnification effect.
+ if (scale >= MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
Settings.Secure.putFloatForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale,
UserHandle.USER_CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index dad6ebe..c8cf5d7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -55,7 +55,6 @@
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserManager;
-import android.util.DisplayUtils;
import android.util.Log;
import android.util.RotationUtils;
import android.util.SparseBooleanArray;
@@ -69,6 +68,8 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.os.SomeArgs;
import com.android.internal.widget.LockPatternUtils;
+import com.android.settingslib.udfps.UdfpsOverlayParams;
+import com.android.settingslib.udfps.UdfpsUtils;
import com.android.systemui.CoreStartable;
import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
@@ -169,6 +170,7 @@
@NonNull private final UserManager mUserManager;
@NonNull private final LockPatternUtils mLockPatternUtils;
@NonNull private final InteractionJankMonitor mInteractionJankMonitor;
+ @NonNull private final UdfpsUtils mUdfpsUtils;
private final @Background DelayableExecutor mBackgroundExecutor;
private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
@@ -578,17 +580,7 @@
*/
private void updateSensorLocations() {
mDisplay.getDisplayInfo(mCachedDisplayInfo);
- final Display.Mode maxDisplayMode =
- DisplayUtils.getMaximumResolutionDisplayMode(mCachedDisplayInfo.supportedModes);
- final float scaleFactor = android.util.DisplayUtils.getPhysicalPixelDisplaySizeRatio(
- maxDisplayMode.getPhysicalWidth(), maxDisplayMode.getPhysicalHeight(),
- mCachedDisplayInfo.getNaturalWidth(), mCachedDisplayInfo.getNaturalHeight());
- if (scaleFactor == Float.POSITIVE_INFINITY) {
- mScaleFactor = 1f;
- } else {
- mScaleFactor = scaleFactor;
- }
-
+ mScaleFactor = mUdfpsUtils.getScaleFactor(mCachedDisplayInfo);
updateUdfpsLocation();
updateFingerprintLocation();
updateFaceLocation();
@@ -732,7 +724,8 @@
@NonNull InteractionJankMonitor jankMonitor,
@Main Handler handler,
@Background DelayableExecutor bgExecutor,
- @NonNull VibratorHelper vibrator) {
+ @NonNull VibratorHelper vibrator,
+ @NonNull UdfpsUtils udfpsUtils) {
mContext = context;
mExecution = execution;
mUserManager = userManager;
@@ -753,6 +746,7 @@
mSfpsEnrolledForUser = new SparseBooleanArray();
mFaceEnrolledForUser = new SparseBooleanArray();
mVibratorHelper = vibrator;
+ mUdfpsUtils = udfpsUtils;
mLogContextInteractor = logContextInteractor;
mBiometricPromptInteractor = biometricPromptInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 64d5518..074928a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -50,10 +50,8 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.util.Log;
-import android.util.RotationUtils;
import android.view.LayoutInflater;
import android.view.MotionEvent;
-import android.view.Surface;
import android.view.VelocityTracker;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -66,6 +64,8 @@
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.settingslib.udfps.UdfpsOverlayParams;
+import com.android.settingslib.udfps.UdfpsUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.biometrics.dagger.BiometricsBackground;
@@ -168,6 +168,7 @@
@NonNull private final SessionTracker mSessionTracker;
@NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@NonNull private final SecureSettings mSecureSettings;
+ @NonNull private final UdfpsUtils mUdfpsUtils;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -266,7 +267,7 @@
mUdfpsDisplayMode, mSecureSettings, requestId, reason, callback,
(view, event, fromUdfpsView) -> onTouch(requestId, event,
fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
- mPrimaryBouncerInteractor, mAlternateBouncerInteractor)));
+ mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils)));
}
@Override
@@ -475,27 +476,6 @@
&& mOverlayParams.getSensorBounds().contains((int) x, (int) y);
}
- private Point getTouchInNativeCoordinates(@NonNull MotionEvent event, int idx) {
- Point portraitTouch = new Point(
- (int) event.getRawX(idx),
- (int) event.getRawY(idx)
- );
- final int rot = mOverlayParams.getRotation();
- if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
- RotationUtils.rotatePoint(portraitTouch,
- RotationUtils.deltaRotation(rot, Surface.ROTATION_0),
- mOverlayParams.getLogicalDisplayWidth(),
- mOverlayParams.getLogicalDisplayHeight()
- );
- }
-
- // Scale the coordinates to native resolution.
- final float scale = mOverlayParams.getScaleFactor();
- portraitTouch.x = (int) (portraitTouch.x / scale);
- portraitTouch.y = (int) (portraitTouch.y / scale);
- return portraitTouch;
- }
-
private void tryDismissingKeyguard() {
if (!mOnFingerDown) {
playStartHaptic();
@@ -713,7 +693,8 @@
break;
}
// Map the touch to portrait mode if the device is in landscape mode.
- final Point scaledTouch = getTouchInNativeCoordinates(event, idx);
+ final Point scaledTouch = mUdfpsUtils.getTouchInNativeCoordinates(
+ idx, event, mOverlayParams);
if (actionMoveWithinSensorArea) {
if (mVelocityTracker == null) {
// touches could be injected, so the velocity tracker may not have
@@ -757,16 +738,7 @@
+ "but serverRequest is null");
return;
}
- // Scale the coordinates to native resolution.
- final float scale = mOverlayParams.getScaleFactor();
- final float scaledSensorX =
- mOverlayParams.getSensorBounds().centerX() / scale;
- final float scaledSensorY =
- mOverlayParams.getSensorBounds().centerY() / scale;
-
- mOverlay.onTouchOutsideOfSensorArea(
- scaledTouch.x, scaledTouch.y, scaledSensorX, scaledSensorY,
- mOverlayParams.getRotation());
+ mOverlay.onTouchOutsideOfSensorArea(scaledTouch);
});
}
}
@@ -838,7 +810,8 @@
@NonNull SinglePointerTouchProcessor singlePointerTouchProcessor,
@NonNull SessionTracker sessionTracker,
@NonNull AlternateBouncerInteractor alternateBouncerInteractor,
- @NonNull SecureSettings secureSettings) {
+ @NonNull SecureSettings secureSettings,
+ @NonNull UdfpsUtils udfpsUtils) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -880,6 +853,7 @@
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
mSecureSettings = secureSettings;
+ mUdfpsUtils = udfpsUtils;
mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
? singlePointerTouchProcessor : null;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 45ca24d..55bacef 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -20,6 +20,7 @@
import android.annotation.UiThread
import android.content.Context
import android.graphics.PixelFormat
+import android.graphics.Point
import android.graphics.Rect
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
@@ -46,6 +47,8 @@
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.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
@@ -101,6 +104,7 @@
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
+ private val udfpsUtils: UdfpsUtils
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
@@ -239,7 +243,9 @@
FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) {
// Enroll udfps UI is handled by settings, so use empty view here
UdfpsFpmEmptyViewController(
- view.addUdfpsView(R.layout.udfps_fpm_empty_view),
+ view.addUdfpsView(R.layout.udfps_fpm_empty_view){
+ updateAccessibilityViewLocation(sensorBounds)
+ },
statusBarStateController,
shadeExpansionStateManager,
dialogManager,
@@ -348,81 +354,18 @@
* the angle to a list of help messages which are announced if accessibility is enabled.
*
*/
- fun onTouchOutsideOfSensorArea(
- touchX: Float,
- touchY: Float,
- sensorX: Float,
- sensorY: Float,
- rotation: Int
- ) {
-
- if (!touchExplorationEnabled) {
- return
+ fun onTouchOutsideOfSensorArea(scaledTouch: Point) {
+ val theStr =
+ udfpsUtils.onTouchOutsideOfSensorArea(
+ touchExplorationEnabled,
+ context,
+ scaledTouch.x,
+ scaledTouch.y,
+ overlayParams
+ )
+ if (theStr != null) {
+ animationViewController?.doAnnounceForAccessibility(theStr)
}
- val touchHints =
- context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
- if (touchHints.size != 4) {
- Log.e(TAG, "expected exactly 4 touch hints, got $touchHints.size?")
- return
- }
- val theStr = onTouchOutsideOfSensorAreaImpl(touchX, touchY, sensorX, sensorY, rotation)
- Log.v(TAG, "Announcing touch outside : " + theStr)
- animationViewController?.doAnnounceForAccessibility(theStr)
- }
-
- /**
- * This function computes the angle of touch relative to the sensor and maps
- * the angle to a list of help messages which are announced if accessibility is enabled.
- *
- * There are 4 quadrants of the circle (90 degree arcs)
- *
- * [315, 360] && [0, 45) -> touchHints[0] = "Move Fingerprint to the left"
- * [45, 135) -> touchHints[1] = "Move Fingerprint down"
- * And so on.
- */
- fun onTouchOutsideOfSensorAreaImpl(
- touchX: Float,
- touchY: Float,
- sensorX: Float,
- sensorY: Float,
- rotation: Int
- ): String {
- val touchHints =
- context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
-
- val xRelativeToSensor = touchX - sensorX
- // Touch coordinates are with respect to the upper left corner, so reverse
- // this calculation
- val yRelativeToSensor = sensorY - touchY
-
- var angleInRad =
- Math.atan2(yRelativeToSensor.toDouble(), xRelativeToSensor.toDouble())
- // If the radians are negative, that means we are counting clockwise.
- // So we need to add 360 degrees
- if (angleInRad < 0.0) {
- angleInRad += 2.0 * Math.PI
- }
- // rad to deg conversion
- val degrees = Math.toDegrees(angleInRad)
-
- val degreesPerBucket = 360.0 / touchHints.size
- val halfBucketDegrees = degreesPerBucket / 2.0
- // The mapping should be as follows
- // [315, 360] && [0, 45] -> 0
- // [45, 135] -> 1
- var index = (((degrees + halfBucketDegrees) % 360) / degreesPerBucket).toInt()
- index %= touchHints.size
-
- // A rotation of 90 degrees corresponds to increasing the index by 1.
- if (rotation == Surface.ROTATION_90) {
- index = (index + 1) % touchHints.size
- }
-
- if (rotation == Surface.ROTATION_270) {
- index = (index + 3) % touchHints.size
- }
-
- return touchHints[index]
}
/** Cancel this request. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
index e8f041e..8352d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
@@ -16,7 +16,11 @@
package com.android.systemui.biometrics
import android.content.Context
+import android.graphics.Rect
import android.util.AttributeSet
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.R
/**
* View corresponding with udfps_fpm_empty_view.xml
@@ -32,4 +36,13 @@
private val fingerprintDrawable: UdfpsFpDrawable = UdfpsFpDrawable(context)
override fun getDrawable(): UdfpsDrawable = fingerprintDrawable
+
+ fun updateAccessibilityViewLocation(sensorBounds: Rect) {
+ val fingerprintAccessibilityView: View = findViewById(R.id.udfps_enroll_accessibility_view)
+ val params: ViewGroup.LayoutParams = fingerprintAccessibilityView.layoutParams
+ params.width = sensorBounds.width()
+ params.height = sensorBounds.height()
+ fingerprintAccessibilityView.layoutParams = params
+ fingerprintAccessibilityView.requestLayout()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
index 802b9b6..079c0b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
@@ -32,6 +32,7 @@
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.settingslib.udfps.UdfpsOverlayParams
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
index 4e6a06b..28ca41d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
@@ -25,6 +25,7 @@
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.FrameLayout
+import com.android.settingslib.udfps.UdfpsOverlayParams
private const val TAG = "UdfpsOverlayView"
private const val POINT_SIZE = 10f
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index e61c614..06dee7a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -26,6 +26,7 @@
import android.util.Log
import android.view.MotionEvent
import android.widget.FrameLayout
+import com.android.settingslib.udfps.UdfpsOverlayParams
import com.android.systemui.R
import com.android.systemui.doze.DozeReceiver
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index 6f8efba..67d2f30 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics.dagger
+import com.android.settingslib.udfps.UdfpsUtils
import com.android.systemui.biometrics.data.repository.PromptRepository
import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl
import com.android.systemui.biometrics.domain.interactor.CredentialInteractor
@@ -54,6 +55,9 @@
@BiometricsBackground
fun providesPluginExecutor(threadFactory: ThreadFactory): Executor =
threadFactory.buildExecutorOnNewThread("biometrics")
+
+ @Provides
+ fun providesUdfpsUtils(): UdfpsUtils = UdfpsUtils()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
index 39ea936..234b383 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
@@ -21,7 +21,7 @@
import android.view.MotionEvent
import android.view.MotionEvent.INVALID_POINTER_ID
import android.view.Surface
-import com.android.systemui.biometrics.UdfpsOverlayParams
+import com.android.settingslib.udfps.UdfpsOverlayParams
import com.android.systemui.biometrics.udfps.TouchProcessorResult.Failure
import com.android.systemui.biometrics.udfps.TouchProcessorResult.ProcessedTouch
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt
index ffcebf9..4bf0ef6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt
@@ -17,7 +17,7 @@
package com.android.systemui.biometrics.udfps
import android.view.MotionEvent
-import com.android.systemui.biometrics.UdfpsOverlayParams
+import com.android.settingslib.udfps.UdfpsOverlayParams
/**
* Determines whether a finger entered or left the area of the under-display fingerprint sensor
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e1aed65..43fb39f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -492,7 +492,7 @@
// TODO(b/238475428): Tracking Bug
@JvmField
val WM_SHADE_ALLOW_BACK_GESTURE =
- unreleasedFlag(1207, "persist.wm.debug.shade_allow_back_gesture", teamfood = false)
+ sysPropBooleanFlag(1207, "persist.wm.debug.shade_allow_back_gesture", default = false)
// TODO(b/238475428): Tracking Bug
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index a692ad7..52d4171 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -28,12 +28,15 @@
import android.os.UserHandle
import android.view.ViewGroup
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider
import com.android.internal.app.ChooserActivity
import com.android.internal.app.ResolverListController
import com.android.internal.app.chooser.NotSelectableTargetInfo
import com.android.internal.app.chooser.TargetInfo
import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
@@ -47,6 +50,7 @@
class MediaProjectionAppSelectorActivity(
private val componentFactory: MediaProjectionAppSelectorComponent.Factory,
private val activityLauncher: AsyncActivityLauncher,
+ private val featureFlags: FeatureFlags,
/** This is used to override the dependency in a screenshot test */
@VisibleForTesting
private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?
@@ -56,7 +60,8 @@
constructor(
componentFactory: MediaProjectionAppSelectorComponent.Factory,
activityLauncher: AsyncActivityLauncher,
- ) : this(componentFactory, activityLauncher, null)
+ featureFlags: FeatureFlags
+ ) : this(componentFactory, activityLauncher, featureFlags, listControllerFactory = null)
private lateinit var configurationController: ConfigurationController
private lateinit var controller: MediaProjectionAppSelectorController
@@ -91,6 +96,13 @@
override fun appliedThemeResId(): Int = R.style.Theme_SystemUI_MediaProjectionAppSelector
+ override fun createBlockerEmptyStateProvider(): EmptyStateProvider =
+ if (featureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
+ component.emptyStateProvider
+ } else {
+ super.createBlockerEmptyStateProvider()
+ }
+
override fun createListController(userHandle: UserHandle): ResolverListController =
listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index d830fc4..c4e76b2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -45,33 +45,46 @@
import android.util.Log;
import android.view.Window;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
import com.android.systemui.screenrecord.MediaProjectionPermissionDialog;
import com.android.systemui.screenrecord.ScreenShareOption;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.util.Utils;
+import javax.inject.Inject;
+
+import dagger.Lazy;
+
public class MediaProjectionPermissionActivity extends Activity
implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
private static final String TAG = "MediaProjectionPermissionActivity";
private static final float MAX_APP_NAME_SIZE_PX = 500f;
private static final String ELLIPSIS = "\u2026";
+ private final FeatureFlags mFeatureFlags;
+ private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
+
private String mPackageName;
private int mUid;
private IMediaProjectionManager mService;
- private FeatureFlags mFeatureFlags;
private AlertDialog mDialog;
+ @Inject
+ public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
+ Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) {
+ mFeatureFlags = featureFlags;
+ mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
+ }
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- mFeatureFlags = Dependency.get(FeatureFlags.class);
mPackageName = getCallingPackage();
IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
mService = IMediaProjectionManager.Stub.asInterface(b);
@@ -104,6 +117,12 @@
return;
}
+ if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
+ if (showScreenCaptureDisabledDialogIfNeeded()) {
+ return;
+ }
+ }
+
TextPaint paint = new TextPaint();
paint.setTextSize(42);
@@ -171,16 +190,7 @@
mDialog = dialogBuilder.create();
}
- SystemUIDialog.registerDismissListener(mDialog);
- SystemUIDialog.applyFlags(mDialog);
- SystemUIDialog.setDialogSize(mDialog);
-
- mDialog.setOnCancelListener(this);
- mDialog.create();
- mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
-
- final Window w = mDialog.getWindow();
- w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ setUpDialog(mDialog);
mDialog.show();
}
@@ -200,6 +210,32 @@
}
}
+ private void setUpDialog(AlertDialog dialog) {
+ SystemUIDialog.registerDismissListener(dialog);
+ SystemUIDialog.applyFlags(dialog);
+ SystemUIDialog.setDialogSize(dialog);
+
+ dialog.setOnCancelListener(this);
+ dialog.create();
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+
+ final Window w = dialog.getWindow();
+ w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ }
+
+ private boolean showScreenCaptureDisabledDialogIfNeeded() {
+ final UserHandle hostUserHandle = getHostUserHandle();
+ if (mScreenCaptureDevicePolicyResolver.get()
+ .isScreenCaptureCompletelyDisabled(hostUserHandle)) {
+ AlertDialog dialog = new ScreenCaptureDisabledDialog(this);
+ setUpDialog(dialog);
+ dialog.show();
+ return true;
+ }
+
+ return false;
+ }
+
private void grantMediaProjectionPermission(int screenShareMode) {
try {
if (screenShareMode == ENTIRE_SCREEN) {
@@ -211,7 +247,7 @@
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
projection.asBinder());
intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
- UserHandle.getUserHandleForUid(getLaunchedFromUid()));
+ getHostUserHandle());
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
// Start activity from the current foreground user to avoid creating a separate
@@ -230,6 +266,10 @@
}
}
+ private UserHandle getHostUserHandle() {
+ return UserHandle.getUserHandleForUid(getLaunchedFromUid());
+ }
+
private IMediaProjection createProjection(int uid, String packageName) throws RemoteException {
return mService.createProjection(uid, packageName,
MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index f1acae8..997370b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -34,7 +34,7 @@
init {
setupShader(RippleShader.RippleShape.CIRCLE)
- setRippleFill(true)
+ setupRippleFadeParams()
setSparkleStrength(0f)
isStarted = false
}
@@ -72,7 +72,7 @@
animator.removeAllUpdateListeners()
// Only show the outline as ripple expands and disappears when animation ends.
- setRippleFill(false)
+ removeRippleFill()
val startingPercentage = calculateStartingPercentage(newHeight)
animator.duration = EXPAND_TO_FULL_DURATION
@@ -103,6 +103,32 @@
return 1 - remainingPercentage
}
+ private fun setupRippleFadeParams() {
+ with(rippleShader) {
+ // No fade out for the base ring.
+ baseRingFadeParams.fadeOutStart = 1f
+ baseRingFadeParams.fadeOutEnd = 1f
+
+ // No fade in and outs for the center fill, as we always draw it.
+ centerFillFadeParams.fadeInStart = 0f
+ centerFillFadeParams.fadeInEnd = 0f
+ centerFillFadeParams.fadeOutStart = 1f
+ centerFillFadeParams.fadeOutEnd = 1f
+ }
+ }
+
+ private fun removeRippleFill() {
+ with(rippleShader) {
+ baseRingFadeParams.fadeOutStart = RippleShader.DEFAULT_BASE_RING_FADE_OUT_START
+ baseRingFadeParams.fadeOutEnd = RippleShader.DEFAULT_FADE_OUT_END
+
+ centerFillFadeParams.fadeInStart = RippleShader.DEFAULT_FADE_IN_START
+ centerFillFadeParams.fadeInEnd = RippleShader.DEFAULT_CENTER_FILL_FADE_IN_END
+ centerFillFadeParams.fadeOutStart = RippleShader.DEFAULT_CENTER_FILL_FADE_OUT_START
+ centerFillFadeParams.fadeOutEnd = RippleShader.DEFAULT_CENTER_FILL_FADE_OUT_END
+ }
+ }
+
companion object {
const val DEFAULT_DURATION = 333L
const val EXPAND_TO_FULL_DURATION = 1000L
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index e665d83..1678c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -24,6 +24,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.MediaProjectionAppSelectorActivity
import com.android.systemui.media.MediaProjectionAppSelectorActivity.Companion.EXTRA_HOST_APP_USER_HANDLE
+import com.android.systemui.media.MediaProjectionPermissionActivity
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
@@ -45,10 +46,10 @@
import dagger.Subcomponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
-import javax.inject.Qualifier
-import javax.inject.Scope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
+import javax.inject.Qualifier
+import javax.inject.Scope
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector
@@ -67,6 +68,11 @@
fun provideMediaProjectionAppSelectorActivity(
activity: MediaProjectionAppSelectorActivity
): Activity
+
+ @Binds
+ @IntoMap
+ @ClassKey(MediaProjectionPermissionActivity::class)
+ fun bindsMediaProjectionPermissionActivity(impl: MediaProjectionPermissionActivity): Activity
}
/**
@@ -149,6 +155,7 @@
val controller: MediaProjectionAppSelectorController
val recentsViewController: MediaProjectionRecentsViewController
+ val emptyStateProvider: MediaProjectionBlockerEmptyStateProvider
@get:HostUserHandle val hostUserHandle: UserHandle
@get:PersonalProfile val personalProfileUserHandle: UserHandle
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
index 2822435..f335733 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -1,14 +1,20 @@
package com.android.systemui.navigationbar.gestural
import android.content.Context
+import android.content.res.Configuration
+import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
+import android.util.MathUtils.min
+import android.util.TypedValue
import android.view.View
+import androidx.appcompat.view.ContextThemeWrapper
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
+import com.android.internal.R.style.Theme_DeviceDefault
import com.android.internal.util.LatencyTracker
import com.android.settingslib.Utils
import com.android.systemui.navigationbar.gestural.BackPanelController.DelayedOnAnimationEndListener
@@ -16,7 +22,10 @@
private const val TAG = "BackPanel"
private const val DEBUG = false
-class BackPanel(context: Context, private val latencyTracker: LatencyTracker) : View(context) {
+class BackPanel(
+ context: Context,
+ private val latencyTracker: LatencyTracker
+) : View(context) {
var arrowsPointLeft = false
set(value) {
@@ -45,52 +54,54 @@
/**
* The length of the arrow measured horizontally. Used for animating [arrowPath]
*/
- private var arrowLength = AnimatedFloat("arrowLength", SpringForce())
+ private var arrowLength = AnimatedFloat(
+ name = "arrowLength",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS
+ )
/**
* The height of the arrow measured vertically from its center to its top (i.e. half the total
* height). Used for animating [arrowPath]
*/
- private var arrowHeight = AnimatedFloat("arrowHeight", SpringForce())
-
- private val backgroundWidth = AnimatedFloat(
- name = "backgroundWidth",
- SpringForce().apply {
- stiffness = 600f
- dampingRatio = 0.65f
- }
+ var arrowHeight = AnimatedFloat(
+ name = "arrowHeight",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ROTATION_DEGREES
)
- private val backgroundHeight = AnimatedFloat(
- name = "backgroundHeight",
- SpringForce().apply {
- stiffness = 600f
- dampingRatio = 0.65f
- }
+ val backgroundWidth = AnimatedFloat(
+ name = "backgroundWidth",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
+ minimumValue = 0f,
+ )
+
+ val backgroundHeight = AnimatedFloat(
+ name = "backgroundHeight",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
+ minimumValue = 0f,
)
/**
* Corners of the background closer to the edge of the screen (where the arrow appeared from).
* Used for animating [arrowBackgroundRect]
*/
- private val backgroundEdgeCornerRadius = AnimatedFloat(
- name = "backgroundEdgeCornerRadius",
- SpringForce().apply {
- stiffness = 400f
- dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
- }
- )
+ val backgroundEdgeCornerRadius = AnimatedFloat("backgroundEdgeCornerRadius")
/**
* Corners of the background further from the edge of the screens (toward the direction the
* arrow is being dragged). Used for animating [arrowBackgroundRect]
*/
- private val backgroundFarCornerRadius = AnimatedFloat(
- name = "backgroundDragCornerRadius",
- SpringForce().apply {
- stiffness = 2200f
- dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
- }
+ val backgroundFarCornerRadius = AnimatedFloat("backgroundFarCornerRadius")
+
+ var scale = AnimatedFloat(
+ name = "scale",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_SCALE,
+ minimumValue = 0f
+ )
+
+ val scalePivotX = AnimatedFloat(
+ name = "scalePivotX",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
+ minimumValue = backgroundWidth.pos / 2,
)
/**
@@ -98,34 +109,40 @@
* background's margin relative to the screen edge. The arrow will be centered within the
* background.
*/
- private var horizontalTranslation = AnimatedFloat("horizontalTranslation", SpringForce())
+ var horizontalTranslation = AnimatedFloat(name = "horizontalTranslation")
- private val currentAlpha: FloatPropertyCompat<BackPanel> =
- object : FloatPropertyCompat<BackPanel>("currentAlpha") {
- override fun setValue(panel: BackPanel, value: Float) {
- panel.alpha = value
- }
+ var arrowAlpha = AnimatedFloat(
+ name = "arrowAlpha",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ALPHA,
+ minimumValue = 0f,
+ maximumValue = 1f
+ )
- override fun getValue(panel: BackPanel): Float = panel.alpha
- }
+ val backgroundAlpha = AnimatedFloat(
+ name = "backgroundAlpha",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ALPHA,
+ minimumValue = 0f,
+ maximumValue = 1f
+ )
- private val alphaAnimation = SpringAnimation(this, currentAlpha)
- .setSpring(
- SpringForce()
- .setStiffness(60f)
- .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
- )
+ private val allAnimatedFloat = setOf(
+ arrowLength,
+ arrowHeight,
+ backgroundWidth,
+ backgroundEdgeCornerRadius,
+ backgroundFarCornerRadius,
+ scalePivotX,
+ scale,
+ horizontalTranslation,
+ arrowAlpha,
+ backgroundAlpha
+ )
/**
* Canvas vertical translation. How far up/down the arrow and background appear relative to the
* canvas.
*/
- private var verticalTranslation: AnimatedFloat = AnimatedFloat(
- name = "verticalTranslation",
- SpringForce().apply {
- stiffness = SpringForce.STIFFNESS_MEDIUM
- }
- )
+ var verticalTranslation = AnimatedFloat("verticalTranslation")
/**
* Use for drawing debug info. Can only be set if [DEBUG]=true
@@ -136,28 +153,67 @@
}
internal fun updateArrowPaint(arrowThickness: Float) {
- // Arrow constants
+
arrowPaint.strokeWidth = arrowThickness
- arrowPaint.color =
- Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
- arrowBackgroundPaint.color = Utils.getColorAccentDefaultColor(context)
+ val isDeviceInNightTheme = resources.configuration.uiMode and
+ Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
+
+ val colorControlActivated = ContextThemeWrapper(context, Theme_DeviceDefault)
+ .run {
+ val typedValue = TypedValue()
+ val a: TypedArray = obtainStyledAttributes(typedValue.data,
+ intArrayOf(android.R.attr.colorControlActivated))
+ val color = a.getColor(0, 0)
+ a.recycle()
+ color
+ }
+
+ val colorPrimary =
+ Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
+
+ arrowPaint.color = Utils.getColorAccentDefaultColor(context)
+
+ arrowBackgroundPaint.color = if (isDeviceInNightTheme) {
+ colorPrimary
+ } else {
+ colorControlActivated
+ }
}
- private inner class AnimatedFloat(name: String, springForce: SpringForce) {
+ inner class AnimatedFloat(
+ name: String,
+ private val minimumVisibleChange: Float? = null,
+ private val minimumValue: Float? = null,
+ private val maximumValue: Float? = null,
+ ) {
+
// The resting position when not stretched by a touch drag
private var restingPosition = 0f
// The current position as updated by the SpringAnimation
var pos = 0f
- set(v) {
+ private set(v) {
if (field != v) {
field = v
invalidate()
}
}
- val animation: SpringAnimation
+ private val animation: SpringAnimation
+ var spring: SpringForce
+ get() = animation.spring
+ set(value) {
+ animation.cancel()
+ animation.spring = value
+ }
+
+ val isRunning: Boolean
+ get() = animation.isRunning
+
+ fun addEndListener(listener: DelayedOnAnimationEndListener) {
+ animation.addEndListener(listener)
+ }
init {
val floatProp = object : FloatPropertyCompat<AnimatedFloat>(name) {
@@ -167,8 +223,12 @@
override fun getValue(animatedFloat: AnimatedFloat): Float = animatedFloat.pos
}
- animation = SpringAnimation(this, floatProp)
- animation.spring = springForce
+ animation = SpringAnimation(this, floatProp).apply {
+ spring = SpringForce()
+ this@AnimatedFloat.minimumValue?.let { setMinValue(it) }
+ this@AnimatedFloat.maximumValue?.let { setMaxValue(it) }
+ this@AnimatedFloat.minimumVisibleChange?.let { minimumVisibleChange = it }
+ }
}
fun snapTo(newPosition: Float) {
@@ -178,8 +238,24 @@
pos = newPosition
}
- fun stretchTo(stretchAmount: Float) {
- animation.animateToFinalPosition(restingPosition + stretchAmount)
+ fun snapToRestingPosition() {
+ snapTo(restingPosition)
+ }
+
+
+ fun stretchTo(
+ stretchAmount: Float,
+ startingVelocity: Float? = null,
+ springForce: SpringForce? = null
+ ) {
+ animation.apply {
+ startingVelocity?.let {
+ cancel()
+ setStartVelocity(it)
+ }
+ springForce?.let { spring = springForce }
+ animateToFinalPosition(restingPosition + stretchAmount)
+ }
}
/**
@@ -188,18 +264,23 @@
*
* The [restingPosition] will remain unchanged. Only the animation is updated.
*/
- fun stretchBy(finalPosition: Float, amount: Float) {
- val stretchedAmount = amount * (finalPosition - restingPosition)
+ fun stretchBy(finalPosition: Float?, amount: Float) {
+ val stretchedAmount = amount * ((finalPosition ?: 0f) - restingPosition)
animation.animateToFinalPosition(restingPosition + stretchedAmount)
}
- fun updateRestingPosition(pos: Float, animated: Boolean) {
+ fun updateRestingPosition(pos: Float?, animated: Boolean = true) {
+ if (pos == null) return
+
restingPosition = pos
- if (animated)
+ if (animated) {
animation.animateToFinalPosition(restingPosition)
- else
+ } else {
snapTo(restingPosition)
+ }
}
+
+ fun cancel() = animation.cancel()
}
init {
@@ -224,126 +305,203 @@
return arrowPath
}
- fun addEndListener(endListener: DelayedOnAnimationEndListener): Boolean {
- return if (alphaAnimation.isRunning) {
- alphaAnimation.addEndListener(endListener)
- true
- } else if (horizontalTranslation.animation.isRunning) {
- horizontalTranslation.animation.addEndListener(endListener)
+ fun addAnimationEndListener(
+ animatedFloat: AnimatedFloat,
+ endListener: DelayedOnAnimationEndListener
+ ): Boolean {
+ return if (animatedFloat.isRunning) {
+ animatedFloat.addEndListener(endListener)
true
} else {
- endListener.runNow()
+ endListener.run()
false
}
}
+ fun cancelAnimations() {
+ allAnimatedFloat.forEach { it.cancel() }
+ }
+
fun setStretch(
- horizontalTranslationStretchAmount: Float,
- arrowStretchAmount: Float,
- backgroundWidthStretchAmount: Float,
- fullyStretchedDimens: EdgePanelParams.BackIndicatorDimens
+ horizontalTranslationStretchAmount: Float,
+ arrowStretchAmount: Float,
+ arrowAlphaStretchAmount: Float,
+ backgroundAlphaStretchAmount: Float,
+ backgroundWidthStretchAmount: Float,
+ backgroundHeightStretchAmount: Float,
+ edgeCornerStretchAmount: Float,
+ farCornerStretchAmount: Float,
+ fullyStretchedDimens: EdgePanelParams.BackIndicatorDimens
) {
horizontalTranslation.stretchBy(
- finalPosition = fullyStretchedDimens.horizontalTranslation,
- amount = horizontalTranslationStretchAmount
+ finalPosition = fullyStretchedDimens.horizontalTranslation,
+ amount = horizontalTranslationStretchAmount
)
arrowLength.stretchBy(
- finalPosition = fullyStretchedDimens.arrowDimens.length,
- amount = arrowStretchAmount
+ finalPosition = fullyStretchedDimens.arrowDimens.length,
+ amount = arrowStretchAmount
)
arrowHeight.stretchBy(
- finalPosition = fullyStretchedDimens.arrowDimens.height,
- amount = arrowStretchAmount
+ finalPosition = fullyStretchedDimens.arrowDimens.height,
+ amount = arrowStretchAmount
+ )
+ arrowAlpha.stretchBy(
+ finalPosition = fullyStretchedDimens.arrowDimens.alpha,
+ amount = arrowAlphaStretchAmount
+ )
+ backgroundAlpha.stretchBy(
+ finalPosition = fullyStretchedDimens.backgroundDimens.alpha,
+ amount = backgroundAlphaStretchAmount
)
backgroundWidth.stretchBy(
- finalPosition = fullyStretchedDimens.backgroundDimens.width,
- amount = backgroundWidthStretchAmount
+ finalPosition = fullyStretchedDimens.backgroundDimens.width,
+ amount = backgroundWidthStretchAmount
+ )
+ backgroundHeight.stretchBy(
+ finalPosition = fullyStretchedDimens.backgroundDimens.height,
+ amount = backgroundHeightStretchAmount
+ )
+ backgroundEdgeCornerRadius.stretchBy(
+ finalPosition = fullyStretchedDimens.backgroundDimens.edgeCornerRadius,
+ amount = edgeCornerStretchAmount
+ )
+ backgroundFarCornerRadius.stretchBy(
+ finalPosition = fullyStretchedDimens.backgroundDimens.farCornerRadius,
+ amount = farCornerStretchAmount
)
}
+ fun popOffEdge(startingVelocity: Float) {
+ val heightStretchAmount = startingVelocity * 50
+ val widthStretchAmount = startingVelocity * 150
+ val scaleStretchAmount = startingVelocity * 0.8f
+ backgroundHeight.stretchTo(stretchAmount = 0f, startingVelocity = -heightStretchAmount)
+ backgroundWidth.stretchTo(stretchAmount = 0f, startingVelocity = widthStretchAmount)
+ scale.stretchTo(stretchAmount = 0f, startingVelocity = -scaleStretchAmount)
+ }
+
+ fun popScale(startingVelocity: Float) {
+ scalePivotX.snapTo(backgroundWidth.pos / 2)
+ scale.stretchTo(stretchAmount = 0f, startingVelocity = startingVelocity)
+ }
+
+ fun popArrowAlpha(startingVelocity: Float, springForce: SpringForce? = null) {
+ arrowAlpha.stretchTo(stretchAmount = 0f, startingVelocity = startingVelocity,
+ springForce = springForce)
+ }
+
fun resetStretch() {
- horizontalTranslation.stretchTo(0f)
- arrowLength.stretchTo(0f)
- arrowHeight.stretchTo(0f)
- backgroundWidth.stretchTo(0f)
- backgroundHeight.stretchTo(0f)
- backgroundEdgeCornerRadius.stretchTo(0f)
- backgroundFarCornerRadius.stretchTo(0f)
+ backgroundAlpha.snapTo(1f)
+ verticalTranslation.snapTo(0f)
+ scale.snapTo(1f)
+
+ horizontalTranslation.snapToRestingPosition()
+ arrowLength.snapToRestingPosition()
+ arrowHeight.snapToRestingPosition()
+ arrowAlpha.snapToRestingPosition()
+ backgroundWidth.snapToRestingPosition()
+ backgroundHeight.snapToRestingPosition()
+ backgroundEdgeCornerRadius.snapToRestingPosition()
+ backgroundFarCornerRadius.snapToRestingPosition()
}
/**
* Updates resting arrow and background size not accounting for stretch
*/
internal fun setRestingDimens(
- restingParams: EdgePanelParams.BackIndicatorDimens,
- animate: Boolean
+ restingParams: EdgePanelParams.BackIndicatorDimens,
+ animate: Boolean = true
) {
- horizontalTranslation.updateRestingPosition(restingParams.horizontalTranslation, animate)
+ horizontalTranslation.updateRestingPosition(restingParams.horizontalTranslation)
+ scale.updateRestingPosition(restingParams.scale)
+ arrowAlpha.updateRestingPosition(restingParams.arrowDimens.alpha)
+ backgroundAlpha.updateRestingPosition(restingParams.backgroundDimens.alpha)
+
arrowLength.updateRestingPosition(restingParams.arrowDimens.length, animate)
arrowHeight.updateRestingPosition(restingParams.arrowDimens.height, animate)
+ scalePivotX.updateRestingPosition(restingParams.backgroundDimens.width, animate)
backgroundWidth.updateRestingPosition(restingParams.backgroundDimens.width, animate)
backgroundHeight.updateRestingPosition(restingParams.backgroundDimens.height, animate)
backgroundEdgeCornerRadius.updateRestingPosition(
- restingParams.backgroundDimens.edgeCornerRadius,
- animate
+ restingParams.backgroundDimens.edgeCornerRadius, animate
)
backgroundFarCornerRadius.updateRestingPosition(
- restingParams.backgroundDimens.farCornerRadius,
- animate
+ restingParams.backgroundDimens.farCornerRadius, animate
)
}
fun animateVertically(yPos: Float) = verticalTranslation.stretchTo(yPos)
- fun setArrowStiffness(arrowStiffness: Float, arrowDampingRatio: Float) {
- arrowLength.animation.spring.apply {
- stiffness = arrowStiffness
- dampingRatio = arrowDampingRatio
- }
- arrowHeight.animation.spring.apply {
- stiffness = arrowStiffness
- dampingRatio = arrowDampingRatio
- }
+ fun setSpring(
+ horizontalTranslation: SpringForce? = null,
+ verticalTranslation: SpringForce? = null,
+ scale: SpringForce? = null,
+ arrowLength: SpringForce? = null,
+ arrowHeight: SpringForce? = null,
+ arrowAlpha: SpringForce? = null,
+ backgroundAlpha: SpringForce? = null,
+ backgroundFarCornerRadius: SpringForce? = null,
+ backgroundEdgeCornerRadius: SpringForce? = null,
+ backgroundWidth: SpringForce? = null,
+ backgroundHeight: SpringForce? = null,
+ ) {
+ arrowLength?.let { this.arrowLength.spring = it }
+ arrowHeight?.let { this.arrowHeight.spring = it }
+ arrowAlpha?.let { this.arrowAlpha.spring = it }
+ backgroundAlpha?.let { this.backgroundAlpha.spring = it }
+ backgroundFarCornerRadius?.let { this.backgroundFarCornerRadius.spring = it }
+ backgroundEdgeCornerRadius?.let { this.backgroundEdgeCornerRadius.spring = it }
+ scale?.let { this.scale.spring = it }
+ backgroundWidth?.let { this.backgroundWidth.spring = it }
+ backgroundHeight?.let { this.backgroundHeight.spring = it }
+ horizontalTranslation?.let { this.horizontalTranslation.spring = it }
+ verticalTranslation?.let { this.verticalTranslation.spring = it }
}
override fun hasOverlappingRendering() = false
override fun onDraw(canvas: Canvas) {
- var edgeCorner = backgroundEdgeCornerRadius.pos
+ val edgeCorner = backgroundEdgeCornerRadius.pos
val farCorner = backgroundFarCornerRadius.pos
val halfHeight = backgroundHeight.pos / 2
+ val canvasWidth = width
+ val backgroundWidth = backgroundWidth.pos
+ val scalePivotX = scalePivotX.pos
canvas.save()
- if (!isLeftPanel) canvas.scale(-1f, 1f, width / 2.0f, 0f)
+ if (!isLeftPanel) canvas.scale(-1f, 1f, canvasWidth / 2.0f, 0f)
canvas.translate(
- horizontalTranslation.pos,
- height * 0.5f + verticalTranslation.pos
+ horizontalTranslation.pos,
+ height * 0.5f + verticalTranslation.pos
)
+ canvas.scale(scale.pos, scale.pos, scalePivotX, 0f)
+
val arrowBackground = arrowBackgroundRect.apply {
left = 0f
top = -halfHeight
- right = backgroundWidth.pos
+ right = backgroundWidth
bottom = halfHeight
}.toPathWithRoundCorners(
- topLeft = edgeCorner,
- bottomLeft = edgeCorner,
- topRight = farCorner,
- bottomRight = farCorner
+ topLeft = edgeCorner,
+ bottomLeft = edgeCorner,
+ topRight = farCorner,
+ bottomRight = farCorner
)
- canvas.drawPath(arrowBackground, arrowBackgroundPaint)
+ canvas.drawPath(arrowBackground,
+ arrowBackgroundPaint.apply { alpha = (255 * backgroundAlpha.pos).toInt() })
val dx = arrowLength.pos
val dy = arrowHeight.pos
// How far the arrow bounding box should be from the edge of the screen. Measured from
// either the tip or the back of the arrow, whichever is closer
- var arrowOffset = (backgroundWidth.pos - dx) / 2
+ val arrowOffset = (backgroundWidth - dx) / 2
canvas.translate(
- /* dx= */ arrowOffset,
- /* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */
+ /* dx= */ arrowOffset,
+ /* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */
)
val arrowPointsAwayFromEdge = !arrowsPointLeft.xor(isLeftPanel)
@@ -355,6 +513,8 @@
}
val arrowPath = calculateArrowPath(dx = dx, dy = dy)
+ val arrowPaint = arrowPaint
+ .apply { alpha = (255 * min(arrowAlpha.pos, backgroundAlpha.pos)).toInt() }
canvas.drawPath(arrowPath, arrowPaint)
canvas.restore()
@@ -372,26 +532,17 @@
}
private fun RectF.toPathWithRoundCorners(
- topLeft: Float = 0f,
- topRight: Float = 0f,
- bottomRight: Float = 0f,
- bottomLeft: Float = 0f
+ topLeft: Float = 0f,
+ topRight: Float = 0f,
+ bottomRight: Float = 0f,
+ bottomLeft: Float = 0f
): Path = Path().apply {
val corners = floatArrayOf(
- topLeft, topLeft,
- topRight, topRight,
- bottomRight, bottomRight,
- bottomLeft, bottomLeft
+ topLeft, topLeft,
+ topRight, topRight,
+ bottomRight, bottomRight,
+ bottomLeft, bottomLeft
)
addRoundRect(this@toPathWithRoundCorners, corners, Path.Direction.CW)
}
-
- fun cancelAlphaAnimations() {
- alphaAnimation.cancel()
- alpha = 1f
- }
-
- fun fadeOut() {
- alphaAnimation.animateToFinalPosition(0f)
- }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 1950c69..ae37fb6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -24,18 +24,15 @@
import android.os.SystemClock
import android.os.VibrationEffect
import android.util.Log
-import android.util.MathUtils.constrain
-import android.util.MathUtils.saturate
+import android.util.MathUtils
import android.view.Gravity
import android.view.MotionEvent
import android.view.VelocityTracker
-import android.view.View
import android.view.ViewConfiguration
import android.view.WindowManager
-import android.view.animation.DecelerateInterpolator
-import android.view.animation.PathInterpolator
+import androidx.core.os.postDelayed
+import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation
-import androidx.dynamicanimation.animation.SpringForce
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.MotionEventsHandlerBase
@@ -51,58 +48,42 @@
import kotlin.math.sign
private const val TAG = "BackPanelController"
-private const val DEBUG = false
-
private const val ENABLE_FAILSAFE = true
-private const val FAILSAFE_DELAY_MS: Long = 350
+private const val PX_PER_SEC = 1000
+private const val PX_PER_MS = 1
-/**
- * The time required between the arrow-appears vibration effect and the back-committed vibration
- * effect. If the arrow is flung quickly, the phone only vibrates once. However, if the arrow is
- * held on the screen for a long time, it will vibrate a second time when the back gesture is
- * committed.
- */
-private const val GESTURE_DURATION_FOR_CLICK_MS = 400
+private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
+private const val MIN_DURATION_COMMITTED_ANIMATION = 200L
+private const val MIN_DURATION_ACTIVE_ANIMATION = 300L
+private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
+private const val MIN_DURATION_CONSIDERED_AS_FLING = 100L
-/**
- * The min duration arrow remains on screen during a fling event.
- */
-private const val FLING_MIN_APPEARANCE_DURATION = 235L
+private const val FAILSAFE_DELAY_MS = 350L
+private const val POP_ON_FLING_DELAY = 160L
-/**
- * The min duration arrow remains on screen during a fling event.
- */
-private const val MIN_FLING_VELOCITY = 3000
+private val VIBRATE_ACTIVATED_EFFECT =
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
-/**
- * The amount of rubber banding we do for the vertical translation
- */
-private const val RUBBER_BAND_AMOUNT = 15
+private val VIBRATE_DEACTIVATED_EFFECT =
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)
-private const val ARROW_APPEAR_STIFFNESS = 600f
-private const val ARROW_APPEAR_DAMPING_RATIO = 0.4f
-private const val ARROW_DISAPPEAR_STIFFNESS = 1200f
-private const val ARROW_DISAPPEAR_DAMPING_RATIO = SpringForce.DAMPING_RATIO_NO_BOUNCY
-
-/**
- * The interpolator used to rubber band
- */
-private val RUBBER_BAND_INTERPOLATOR = PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f)
-
-private val DECELERATE_INTERPOLATOR = DecelerateInterpolator()
-
-private val DECELERATE_INTERPOLATOR_SLOW = DecelerateInterpolator(0.7f)
+private const val DEBUG = false
class BackPanelController private constructor(
- context: Context,
- private val windowManager: WindowManager,
- private val viewConfiguration: ViewConfiguration,
- @Main private val mainHandler: Handler,
- private val vibratorHelper: VibratorHelper,
- private val configurationController: ConfigurationController,
- latencyTracker: LatencyTracker
-) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin {
+ context: Context,
+ private val windowManager: WindowManager,
+ private val viewConfiguration: ViewConfiguration,
+ @Main private val mainHandler: Handler,
+ private val vibratorHelper: VibratorHelper,
+ private val configurationController: ConfigurationController,
+ private val latencyTracker: LatencyTracker
+) : ViewController<BackPanel>(
+ BackPanel(
+ context,
+ latencyTracker
+ )
+), NavigationEdgeBackPlugin {
/**
* Injectable instance to create a new BackPanelController.
@@ -111,23 +92,23 @@
* BackPanelController, and we need to match EdgeBackGestureHandler's context.
*/
class Factory @Inject constructor(
- private val windowManager: WindowManager,
- private val viewConfiguration: ViewConfiguration,
- @Main private val mainHandler: Handler,
- private val vibratorHelper: VibratorHelper,
- private val configurationController: ConfigurationController,
- private val latencyTracker: LatencyTracker
+ private val windowManager: WindowManager,
+ private val viewConfiguration: ViewConfiguration,
+ @Main private val mainHandler: Handler,
+ private val vibratorHelper: VibratorHelper,
+ private val configurationController: ConfigurationController,
+ private val latencyTracker: LatencyTracker
) {
/** Construct a [BackPanelController]. */
fun create(context: Context): BackPanelController {
val backPanelController = BackPanelController(
- context,
- windowManager,
- viewConfiguration,
- mainHandler,
- vibratorHelper,
- configurationController,
- latencyTracker
+ context,
+ windowManager,
+ viewConfiguration,
+ mainHandler,
+ vibratorHelper,
+ configurationController,
+ latencyTracker
)
backPanelController.init()
return backPanelController
@@ -138,17 +119,15 @@
private var currentState: GestureState = GestureState.GONE
private var previousState: GestureState = GestureState.GONE
- // Phone should only vibrate the first time the arrow is activated
- private var hasHapticPlayed = false
-
// Screen attributes
private lateinit var layoutParams: WindowManager.LayoutParams
private val displaySize = Point()
private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
-
+ private var previousXTranslationOnActiveOffset = 0f
private var previousXTranslation = 0f
private var totalTouchDelta = 0f
+ private var touchDeltaStartX = 0f
private var velocityTracker: VelocityTracker? = null
set(value) {
if (field != value) field?.recycle()
@@ -162,8 +141,18 @@
// The x,y position of the first touch event
private var startX = 0f
private var startY = 0f
+ private var startIsLeft: Boolean? = null
- private var gestureStartTime = 0L
+ private var gestureSinceActionDown = 0L
+ private var gestureEntryTime = 0L
+ private var gestureActiveTime = 0L
+ private var gestureInactiveOrEntryTime = 0L
+ private var gestureArrowStrokeVisibleTime = 0L
+
+ private val elapsedTimeSinceActionDown
+ get() = SystemClock.uptimeMillis() - gestureSinceActionDown
+ private val elapsedTimeSinceEntry
+ get() = SystemClock.uptimeMillis() - gestureEntryTime
// Whether the current gesture has moved a sufficiently large amount,
// so that we can unambiguously start showing the ENTRY animation
@@ -192,17 +181,6 @@
/* back action currently cancelling, arrow soon to be GONE */
CANCELLED;
-
- /**
- * @return true if the current state responds to touch move events in some way (e.g. by
- * stretching the back indicator)
- */
- fun isInteractive(): Boolean {
- return when (this) {
- ENTRY, ACTIVE, INACTIVE -> true
- GONE, FLUNG, COMMITTED, CANCELLED -> false
- }
- }
}
/**
@@ -210,50 +188,43 @@
* runnable is not called if the animation is cancelled
*/
inner class DelayedOnAnimationEndListener internal constructor(
- private val handler: Handler,
- private val runnable: Runnable,
- private val minDuration: Long
+ private val handler: Handler,
+ private val runnableDelay: Long,
+ val runnable: Runnable,
) : DynamicAnimation.OnAnimationEndListener {
+
override fun onAnimationEnd(
- animation: DynamicAnimation<*>,
- canceled: Boolean,
- value: Float,
- velocity: Float
+ animation: DynamicAnimation<*>,
+ canceled: Boolean,
+ value: Float,
+ velocity: Float
) {
animation.removeEndListener(this)
+
if (!canceled) {
- // Total elapsed time of the gesture and the animation
- val totalElapsedTime = SystemClock.uptimeMillis() - gestureStartTime
+
// The delay between finishing this animation and starting the runnable
- val delay = max(0, minDuration - totalElapsedTime)
+ val delay = max(0, runnableDelay - elapsedTimeSinceEntry)
+
handler.postDelayed(runnable, delay)
}
}
- internal fun runNow() {
- runnable.run()
- }
+ internal fun run() = runnable.run()
}
- private val setCommittedEndListener =
- DelayedOnAnimationEndListener(
- mainHandler,
- { updateArrowState(GestureState.COMMITTED) },
- minDuration = FLING_MIN_APPEARANCE_DURATION
- )
+ private val onEndSetCommittedStateListener = DelayedOnAnimationEndListener(mainHandler, 0L) {
+ updateArrowState(GestureState.COMMITTED)
+ }
- private val setGoneEndListener =
- DelayedOnAnimationEndListener(
- mainHandler,
- {
+
+ private val onEndSetGoneStateListener =
+ DelayedOnAnimationEndListener(mainHandler, runnableDelay = 0L) {
cancelFailsafe()
updateArrowState(GestureState.GONE)
- },
- minDuration = 0
- )
+ }
- // Vibration
- private var vibrationTime: Long = 0
+ private val playAnimationThenSetGoneOnAlphaEnd = Runnable { playAnimationThenSetGoneEnd() }
// Minimum of the screen's width or the predefined threshold
private var fullyStretchedThreshold = 0f
@@ -280,7 +251,7 @@
updateConfiguration()
updateArrowDirection(configurationController.isLayoutRtl)
updateArrowState(GestureState.GONE, force = true)
- updateRestingArrowDimens(animated = false, currentState)
+ updateRestingArrowDimens()
configurationController.addCallback(configurationListener)
}
@@ -297,22 +268,57 @@
velocityTracker!!.addMovement(event)
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
- resetOnDown()
+ gestureSinceActionDown = SystemClock.uptimeMillis()
+ cancelAllPendingAnimations()
startX = event.x
startY = event.y
- gestureStartTime = SystemClock.uptimeMillis()
+
+ updateArrowState(GestureState.GONE)
+ updateYStartPosition(startY)
+
+ // reset animation properties
+ startIsLeft = mView.isLeftPanel
+ hasPassedDragSlop = false
+ mView.resetStretch()
}
MotionEvent.ACTION_MOVE -> {
- // only go to the ENTRY state after some minimum motion has occurred
if (dragSlopExceeded(event.x, startX)) {
handleMoveEvent(event)
}
}
MotionEvent.ACTION_UP -> {
- if (currentState == GestureState.ACTIVE) {
- updateArrowState(if (isFlung()) GestureState.FLUNG else GestureState.COMMITTED)
- } else if (currentState != GestureState.GONE) { // if invisible, skip animation
- updateArrowState(GestureState.CANCELLED)
+ when (currentState) {
+ GestureState.ENTRY -> {
+ if (isFlungAwayFromEdge(endX = event.x)) {
+ updateArrowState(GestureState.ACTIVE)
+ updateArrowState(GestureState.FLUNG)
+ } else {
+ updateArrowState(GestureState.CANCELLED)
+ }
+ }
+ GestureState.INACTIVE -> {
+ if (isFlungAwayFromEdge(endX = event.x)) {
+ mainHandler.postDelayed(MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION) {
+ updateArrowState(GestureState.ACTIVE)
+ updateArrowState(GestureState.FLUNG)
+ }
+ } else {
+ updateArrowState(GestureState.CANCELLED)
+ }
+ }
+ GestureState.ACTIVE -> {
+ if (elapsedTimeSinceEntry < MIN_DURATION_CONSIDERED_AS_FLING) {
+ updateArrowState(GestureState.FLUNG)
+ } else {
+ updateArrowState(GestureState.COMMITTED)
+ }
+ }
+ GestureState.GONE,
+ GestureState.FLUNG,
+ GestureState.COMMITTED,
+ GestureState.CANCELLED -> {
+ updateArrowState(GestureState.CANCELLED)
+ }
}
velocityTracker = null
}
@@ -326,6 +332,14 @@
}
}
+ private fun cancelAllPendingAnimations() {
+ cancelFailsafe()
+ mView.cancelAnimations()
+ mainHandler.removeCallbacks(onEndSetCommittedStateListener.runnable)
+ mainHandler.removeCallbacks(onEndSetGoneStateListener.runnable)
+ mainHandler.removeCallbacks(playAnimationThenSetGoneOnAlphaEnd)
+ }
+
/**
* Returns false until the current gesture exceeds the touch slop threshold,
* and returns true thereafter (we reset on the subsequent back gesture).
@@ -336,7 +350,7 @@
private fun dragSlopExceeded(curX: Float, startX: Float): Boolean {
if (hasPassedDragSlop) return true
- if (abs(curX - startX) > viewConfiguration.scaledTouchSlop) {
+ if (abs(curX - startX) > viewConfiguration.scaledEdgeSlop) {
// Reset the arrow to the side
updateArrowState(GestureState.ENTRY)
@@ -349,39 +363,46 @@
}
private fun updateArrowStateOnMove(yTranslation: Float, xTranslation: Float) {
- if (!currentState.isInteractive())
- return
+
+ val isWithinYActivationThreshold = xTranslation * 2 >= yTranslation
when (currentState) {
- // Check if we should transition from ENTRY to ACTIVE
- GestureState.ENTRY ->
- if (xTranslation > params.swipeTriggerThreshold) {
+ GestureState.ENTRY -> {
+ if (xTranslation > params.staticTriggerThreshold) {
updateArrowState(GestureState.ACTIVE)
}
+ }
+ GestureState.ACTIVE -> {
+ val isPastDynamicDeactivationThreshold =
+ totalTouchDelta <= params.deactivationSwipeTriggerThreshold
+ val isMinDurationElapsed =
+ elapsedTimeSinceActionDown > MIN_DURATION_ACTIVE_ANIMATION
- // Abort if we had continuous motion toward the edge for a while, OR the direction
- // in Y is bigger than X * 2
- GestureState.ACTIVE ->
- if ((totalTouchDelta < 0 && -totalTouchDelta > params.minDeltaForSwitch) ||
- (yTranslation > xTranslation * 2)
+ if (isMinDurationElapsed && (!isWithinYActivationThreshold ||
+ isPastDynamicDeactivationThreshold)
) {
updateArrowState(GestureState.INACTIVE)
}
+ }
+ GestureState.INACTIVE -> {
+ val isPastStaticThreshold =
+ xTranslation > params.staticTriggerThreshold
+ val isPastDynamicReactivationThreshold = totalTouchDelta > 0 &&
+ abs(totalTouchDelta) >=
+ params.reactivationTriggerThreshold
- // Re-activate if we had continuous motion away from the edge for a while
- GestureState.INACTIVE ->
- if (totalTouchDelta > 0 && totalTouchDelta > params.minDeltaForSwitch) {
+ if (isPastStaticThreshold &&
+ isPastDynamicReactivationThreshold &&
+ isWithinYActivationThreshold
+ ) {
updateArrowState(GestureState.ACTIVE)
}
-
- // By default assume the current direction is kept
+ }
else -> {}
}
}
private fun handleMoveEvent(event: MotionEvent) {
- if (!currentState.isInteractive())
- return
val x = event.x
val y = event.y
@@ -401,23 +422,44 @@
previousXTranslation = xTranslation
if (abs(xDelta) > 0) {
- if (sign(xDelta) == sign(totalTouchDelta)) {
+ val range =
+ params.run { deactivationSwipeTriggerThreshold..reactivationTriggerThreshold }
+ val isTouchInContinuousDirection =
+ sign(xDelta) == sign(totalTouchDelta) || totalTouchDelta in range
+
+ if (isTouchInContinuousDirection) {
// Direction has NOT changed, so keep counting the delta
totalTouchDelta += xDelta
} else {
// Direction has changed, so reset the delta
totalTouchDelta = xDelta
+ touchDeltaStartX = x
}
}
updateArrowStateOnMove(yTranslation, xTranslation)
+
when (currentState) {
- GestureState.ACTIVE ->
- stretchActiveBackIndicator(fullScreenStretchProgress(xTranslation))
- GestureState.ENTRY ->
- stretchEntryBackIndicator(preThresholdStretchProgress(xTranslation))
- GestureState.INACTIVE ->
- mView.resetStretch()
+ GestureState.ACTIVE -> {
+ stretchActiveBackIndicator(fullScreenProgress(xTranslation))
+ }
+ GestureState.ENTRY -> {
+ val progress = staticThresholdProgress(xTranslation)
+ stretchEntryBackIndicator(progress)
+
+ params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
+ mView.popArrowAlpha(0f, it.value)
+ }
+ }
+ GestureState.INACTIVE -> {
+ val progress = reactivationThresholdProgress(totalTouchDelta)
+ stretchInactiveBackIndicator(progress)
+
+ params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
+ gestureArrowStrokeVisibleTime = SystemClock.uptimeMillis()
+ mView.popArrowAlpha(0f, it.value)
+ }
+ }
else -> {}
}
@@ -428,21 +470,22 @@
private fun setVerticalTranslation(yOffset: Float) {
val yTranslation = abs(yOffset)
val maxYOffset = (mView.height - params.entryIndicator.backgroundDimens.height) / 2f
- val yProgress = saturate(yTranslation / (maxYOffset * RUBBER_BAND_AMOUNT))
- mView.animateVertically(
- RUBBER_BAND_INTERPOLATOR.getInterpolation(yProgress) * maxYOffset *
+ val rubberbandAmount = 15f
+ val yProgress = MathUtils.saturate(yTranslation / (maxYOffset * rubberbandAmount))
+ val yPosition = params.translationInterpolator.getInterpolation(yProgress) *
+ maxYOffset *
sign(yOffset)
- )
+ mView.animateVertically(yPosition)
}
/**
- * @return the relative position of the drag from the time after the arrow is activated until
+ * Tracks the relative position of the drag from the time after the arrow is activated until
* the arrow is fully stretched (between 0.0 - 1.0f)
*/
- private fun fullScreenStretchProgress(xTranslation: Float): Float {
- return saturate(
- (xTranslation - params.swipeTriggerThreshold) /
- (fullyStretchedThreshold - params.swipeTriggerThreshold)
+ private fun fullScreenProgress(xTranslation: Float): Float {
+ return MathUtils.saturate(
+ (xTranslation - previousXTranslationOnActiveOffset) /
+ (fullyStretchedThreshold - previousXTranslationOnActiveOffset)
)
}
@@ -450,26 +493,74 @@
* Tracks the relative position of the drag from the entry until the threshold where the arrow
* activates (between 0.0 - 1.0f)
*/
- private fun preThresholdStretchProgress(xTranslation: Float): Float {
- return saturate(xTranslation / params.swipeTriggerThreshold)
+ private fun staticThresholdProgress(xTranslation: Float): Float {
+ return MathUtils.saturate(xTranslation / params.staticTriggerThreshold)
+ }
+
+ private fun reactivationThresholdProgress(totalTouchDelta: Float): Float {
+ return MathUtils.saturate(totalTouchDelta / params.reactivationTriggerThreshold)
}
private fun stretchActiveBackIndicator(progress: Float) {
- val rubberBandIterpolation = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
mView.setStretch(
- horizontalTranslationStretchAmount = rubberBandIterpolation,
- arrowStretchAmount = rubberBandIterpolation,
- backgroundWidthStretchAmount = DECELERATE_INTERPOLATOR_SLOW.getInterpolation(progress),
- params.fullyStretchedIndicator
+ horizontalTranslationStretchAmount = params.translationInterpolator
+ .getInterpolation(progress),
+ arrowStretchAmount = params.arrowAngleInterpolator.getInterpolation(progress),
+ backgroundWidthStretchAmount = params.activeWidthInterpolator
+ .getInterpolation(progress),
+ backgroundAlphaStretchAmount = 1f,
+ backgroundHeightStretchAmount = 1f,
+ arrowAlphaStretchAmount = 1f,
+ edgeCornerStretchAmount = 1f,
+ farCornerStretchAmount = 1f,
+ fullyStretchedDimens = params.fullyStretchedIndicator
)
}
private fun stretchEntryBackIndicator(progress: Float) {
mView.setStretch(
- horizontalTranslationStretchAmount = 0f,
- arrowStretchAmount = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress),
- backgroundWidthStretchAmount = DECELERATE_INTERPOLATOR.getInterpolation(progress),
- params.preThresholdIndicator
+ horizontalTranslationStretchAmount = 0f,
+ arrowStretchAmount = params.arrowAngleInterpolator
+ .getInterpolation(progress),
+ backgroundWidthStretchAmount = params.entryWidthInterpolator
+ .getInterpolation(progress),
+ backgroundHeightStretchAmount = params.heightInterpolator
+ .getInterpolation(progress),
+ backgroundAlphaStretchAmount = 1f,
+ arrowAlphaStretchAmount = params.arrowStrokeAlphaInterpolator.get(progress).value,
+ edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress),
+ farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress),
+ fullyStretchedDimens = params.preThresholdIndicator
+ )
+ }
+
+ private var previousPreThresholdWidthInterpolator = params.entryWidthTowardsEdgeInterpolator
+ fun preThresholdWidthStretchAmount(progress: Float): Float {
+ val interpolator = run {
+ val isPastSlop = abs(totalTouchDelta) > ViewConfiguration.get(context).scaledTouchSlop
+ if (isPastSlop) {
+ if (totalTouchDelta > 0) {
+ params.entryWidthInterpolator
+ } else params.entryWidthTowardsEdgeInterpolator
+ } else {
+ previousPreThresholdWidthInterpolator
+ }.also { previousPreThresholdWidthInterpolator = it }
+ }
+ return interpolator.getInterpolation(progress).coerceAtLeast(0f)
+ }
+
+ private fun stretchInactiveBackIndicator(progress: Float) {
+ mView.setStretch(
+ horizontalTranslationStretchAmount = 0f,
+ arrowStretchAmount = params.arrowAngleInterpolator.getInterpolation(progress),
+ backgroundWidthStretchAmount = preThresholdWidthStretchAmount(progress),
+ backgroundHeightStretchAmount = params.heightInterpolator
+ .getInterpolation(progress),
+ backgroundAlphaStretchAmount = 1f,
+ arrowAlphaStretchAmount = params.arrowStrokeAlphaInterpolator.get(progress).value,
+ edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress),
+ farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress),
+ fullyStretchedDimens = params.preThresholdIndicator
)
}
@@ -487,8 +578,7 @@
}
}
- override fun setInsets(insetLeft: Int, insetRight: Int) {
- }
+ override fun setInsets(insetLeft: Int, insetRight: Int) = Unit
override fun setBackCallback(callback: NavigationEdgeBackPlugin.BackCallback) {
backCallback = callback
@@ -500,65 +590,57 @@
}
override fun setMotionEventsHandler(motionEventsHandler: MotionEventsHandlerBase?) {
- TODO("Not yet implemented")
+ // TODO(255697805): Integrate MotionEventHandler for trackpad.
}
- private fun isFlung() = velocityTracker!!.run {
- computeCurrentVelocity(1000)
- abs(xVelocity) > MIN_FLING_VELOCITY
+ private fun isDragAwayFromEdge(velocityPxPerSecThreshold: Int = 0) = velocityTracker!!.run {
+ computeCurrentVelocity(PX_PER_SEC)
+ val velocity = xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1)
+ velocity > velocityPxPerSecThreshold
}
- private fun playFlingBackAnimation() {
- playAnimation(setCommittedEndListener)
+ private fun isFlungAwayFromEdge(endX: Float, startX: Float = touchDeltaStartX): Boolean {
+ val minDistanceConsideredForFling = ViewConfiguration.get(context).scaledTouchSlop
+ val flingDistance = abs(endX - startX)
+ val isPastFlingVelocity = isDragAwayFromEdge(
+ velocityPxPerSecThreshold =
+ ViewConfiguration.get(context).scaledMinimumFlingVelocity)
+ return flingDistance > minDistanceConsideredForFling && isPastFlingVelocity
}
- private fun playCommitBackAnimation() {
- // Check if we should vibrate again
- if (previousState != GestureState.FLUNG) {
- velocityTracker!!.computeCurrentVelocity(1000)
- val isSlow = abs(velocityTracker!!.xVelocity) < 500
- val hasNotVibratedRecently =
- SystemClock.uptimeMillis() - vibrationTime >= GESTURE_DURATION_FOR_CLICK_MS
- if (isSlow || hasNotVibratedRecently) {
- vibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK)
- }
- }
- // Dispatch the actual back trigger
- if (DEBUG) Log.d(TAG, "playCommitBackAnimation() invoked triggerBack() on backCallback")
- backCallback.triggerBack()
-
- playAnimation(setGoneEndListener)
- }
-
- private fun playCancelBackAnimation() {
- backCallback.cancelBack()
- playAnimation(setGoneEndListener)
- }
-
- /**
- * @return true if the animation is running, false otherwise. Some transitions don't animate
- */
- private fun playAnimation(endListener: DelayedOnAnimationEndListener) {
- updateRestingArrowDimens(animated = true, currentState)
-
- if (!mView.addEndListener(endListener)) {
+ private fun playHorizontalAnimationThen(onEnd: DelayedOnAnimationEndListener) {
+ updateRestingArrowDimens()
+ if (!mView.addAnimationEndListener(mView.horizontalTranslation, onEnd)) {
scheduleFailsafe()
}
}
- private fun resetOnDown() {
- hasPassedDragSlop = false
- hasHapticPlayed = false
- totalTouchDelta = 0f
- vibrationTime = 0
- cancelFailsafe()
+ private fun playAnimationThenSetGoneEnd() {
+ updateRestingArrowDimens()
+ if (!mView.addAnimationEndListener(mView.backgroundAlpha, onEndSetGoneStateListener)) {
+ scheduleFailsafe()
+ }
}
- private fun updateYPosition(touchY: Float) {
+ private fun playWithBackgroundWidthAnimation(
+ onEnd: DelayedOnAnimationEndListener,
+ delay: Long = 0L
+ ) {
+ if (delay == 0L) {
+ updateRestingArrowDimens()
+ if (!mView.addAnimationEndListener(mView.backgroundWidth, onEnd)) {
+ scheduleFailsafe()
+ }
+ } else {
+ mainHandler.postDelayed(delay) { playWithBackgroundWidthAnimation(onEnd, delay = 0L) }
+ }
+ }
+
+ private fun updateYStartPosition(touchY: Float) {
var yPosition = touchY - params.fingerOffset
yPosition = max(yPosition, params.minArrowYPosition.toFloat())
yPosition -= layoutParams.height / 2.0f
- layoutParams.y = constrain(yPosition.toInt(), 0, displaySize.y)
+ layoutParams.y = MathUtils.constrain(yPosition.toInt(), 0, displaySize.y)
}
override fun setDisplaySize(displaySize: Point) {
@@ -569,53 +651,135 @@
/**
* Updates resting arrow and background size not accounting for stretch
*/
- private fun updateRestingArrowDimens(animated: Boolean, currentState: GestureState) {
- if (animated) {
- when (currentState) {
- GestureState.ENTRY, GestureState.ACTIVE, GestureState.FLUNG ->
- mView.setArrowStiffness(ARROW_APPEAR_STIFFNESS, ARROW_APPEAR_DAMPING_RATIO)
- GestureState.CANCELLED -> mView.fadeOut()
- else ->
- mView.setArrowStiffness(
- ARROW_DISAPPEAR_STIFFNESS,
- ARROW_DISAPPEAR_DAMPING_RATIO
- )
+ private fun updateRestingArrowDimens() {
+ when (currentState) {
+ GestureState.GONE,
+ GestureState.ENTRY -> {
+ mView.setSpring(
+ arrowLength = params.entryIndicator.arrowDimens.lengthSpring,
+ arrowHeight = params.entryIndicator.arrowDimens.heightSpring,
+ arrowAlpha = params.entryIndicator.arrowDimens.alphaSpring,
+ scale = params.entryIndicator.scaleSpring,
+ verticalTranslation = params.entryIndicator.verticalTranslationSpring,
+ horizontalTranslation = params.entryIndicator.horizontalTranslationSpring,
+ backgroundAlpha = params.entryIndicator.backgroundDimens.alphaSpring,
+ backgroundWidth = params.entryIndicator.backgroundDimens.widthSpring,
+ backgroundHeight = params.entryIndicator.backgroundDimens.heightSpring,
+ backgroundEdgeCornerRadius = params.entryIndicator.backgroundDimens
+ .edgeCornerRadiusSpring,
+ backgroundFarCornerRadius = params.entryIndicator.backgroundDimens
+ .farCornerRadiusSpring,
+ )
}
+ GestureState.INACTIVE -> {
+ mView.setSpring(
+ arrowLength = params.preThresholdIndicator.arrowDimens.lengthSpring,
+ arrowHeight = params.preThresholdIndicator.arrowDimens.heightSpring,
+ horizontalTranslation = params.preThresholdIndicator
+ .horizontalTranslationSpring,
+ scale = params.preThresholdIndicator.scaleSpring,
+ backgroundWidth = params.preThresholdIndicator.backgroundDimens
+ .widthSpring,
+ backgroundHeight = params.preThresholdIndicator.backgroundDimens
+ .heightSpring,
+ backgroundEdgeCornerRadius = params.preThresholdIndicator.backgroundDimens
+ .edgeCornerRadiusSpring,
+ backgroundFarCornerRadius = params.preThresholdIndicator.backgroundDimens
+ .farCornerRadiusSpring,
+ )
+ }
+ GestureState.ACTIVE -> {
+ mView.setSpring(
+ arrowLength = params.activeIndicator.arrowDimens.lengthSpring,
+ arrowHeight = params.activeIndicator.arrowDimens.heightSpring,
+ scale = params.activeIndicator.scaleSpring,
+ horizontalTranslation = params.activeIndicator.horizontalTranslationSpring,
+ backgroundWidth = params.activeIndicator.backgroundDimens.widthSpring,
+ backgroundHeight = params.activeIndicator.backgroundDimens.heightSpring,
+ backgroundEdgeCornerRadius = params.activeIndicator.backgroundDimens
+ .edgeCornerRadiusSpring,
+ backgroundFarCornerRadius = params.activeIndicator.backgroundDimens
+ .farCornerRadiusSpring,
+ )
+ }
+ GestureState.FLUNG -> {
+ mView.setSpring(
+ arrowLength = params.flungIndicator.arrowDimens.lengthSpring,
+ arrowHeight = params.flungIndicator.arrowDimens.heightSpring,
+ backgroundWidth = params.flungIndicator.backgroundDimens.widthSpring,
+ backgroundHeight = params.flungIndicator.backgroundDimens.heightSpring,
+ backgroundEdgeCornerRadius = params.flungIndicator.backgroundDimens
+ .edgeCornerRadiusSpring,
+ backgroundFarCornerRadius = params.flungIndicator.backgroundDimens
+ .farCornerRadiusSpring,
+ )
+ }
+ GestureState.COMMITTED -> {
+ mView.setSpring(
+ arrowLength = params.committedIndicator.arrowDimens.lengthSpring,
+ arrowHeight = params.committedIndicator.arrowDimens.heightSpring,
+ scale = params.committedIndicator.scaleSpring,
+ backgroundWidth = params.committedIndicator.backgroundDimens.widthSpring,
+ backgroundHeight = params.committedIndicator.backgroundDimens.heightSpring,
+ backgroundEdgeCornerRadius = params.committedIndicator.backgroundDimens
+ .edgeCornerRadiusSpring,
+ backgroundFarCornerRadius = params.committedIndicator.backgroundDimens
+ .farCornerRadiusSpring,
+ )
+ }
+ else -> {}
}
+
mView.setRestingDimens(
- restingParams = EdgePanelParams.BackIndicatorDimens(
- horizontalTranslation = when (currentState) {
- GestureState.GONE -> -params.activeIndicator.backgroundDimens.width
- // Position the committed arrow slightly further off the screen so we do not
- // see part of it bouncing
- GestureState.COMMITTED ->
- -params.activeIndicator.backgroundDimens.width * 1.5f
- GestureState.FLUNG -> params.fullyStretchedIndicator.horizontalTranslation
- GestureState.ACTIVE -> params.activeIndicator.horizontalTranslation
- GestureState.ENTRY, GestureState.INACTIVE, GestureState.CANCELLED ->
- params.entryIndicator.horizontalTranslation
- },
- arrowDimens = when (currentState) {
- GestureState.ACTIVE, GestureState.INACTIVE,
- GestureState.COMMITTED, GestureState.FLUNG -> params.activeIndicator.arrowDimens
- GestureState.CANCELLED -> params.cancelledArrowDimens
- GestureState.GONE, GestureState.ENTRY -> params.entryIndicator.arrowDimens
- },
- backgroundDimens = when (currentState) {
- GestureState.GONE, GestureState.ENTRY -> params.entryIndicator.backgroundDimens
- else ->
- params.activeIndicator.backgroundDimens.copy(
- edgeCornerRadius =
- if (currentState == GestureState.INACTIVE ||
- currentState == GestureState.CANCELLED
- )
- params.cancelledEdgeCornerRadius
- else
- params.activeIndicator.backgroundDimens.edgeCornerRadius
- )
- }
- ),
- animate = animated
+ animate = !(currentState == GestureState.FLUNG ||
+ currentState == GestureState.COMMITTED),
+ restingParams = EdgePanelParams.BackIndicatorDimens(
+ scale = when (currentState) {
+ GestureState.ACTIVE,
+ GestureState.FLUNG,
+ -> params.activeIndicator.scale
+ GestureState.COMMITTED -> params.committedIndicator.scale
+ else -> params.preThresholdIndicator.scale
+ },
+ scalePivotX = when (currentState) {
+ GestureState.GONE,
+ GestureState.ENTRY,
+ GestureState.INACTIVE,
+ GestureState.CANCELLED -> params.preThresholdIndicator.scalePivotX
+ else -> params.committedIndicator.scalePivotX
+ },
+ horizontalTranslation = when (currentState) {
+ GestureState.GONE -> {
+ params.activeIndicator.backgroundDimens.width?.times(-1)
+ }
+ GestureState.ENTRY,
+ GestureState.INACTIVE -> params.entryIndicator.horizontalTranslation
+ GestureState.FLUNG -> params.activeIndicator.horizontalTranslation
+ GestureState.ACTIVE -> params.activeIndicator.horizontalTranslation
+ GestureState.CANCELLED -> {
+ params.cancelledIndicator.horizontalTranslation
+ }
+ else -> null
+ },
+ arrowDimens = when (currentState) {
+ GestureState.GONE,
+ GestureState.ENTRY,
+ GestureState.INACTIVE -> params.entryIndicator.arrowDimens
+ GestureState.ACTIVE -> params.activeIndicator.arrowDimens
+ GestureState.FLUNG,
+ GestureState.COMMITTED -> params.committedIndicator.arrowDimens
+ GestureState.CANCELLED -> params.cancelledIndicator.arrowDimens
+ },
+ backgroundDimens = when (currentState) {
+ GestureState.GONE,
+ GestureState.ENTRY,
+ GestureState.INACTIVE -> params.entryIndicator.backgroundDimens
+ GestureState.ACTIVE -> params.activeIndicator.backgroundDimens
+ GestureState.FLUNG -> params.activeIndicator.backgroundDimens
+ GestureState.COMMITTED -> params.committedIndicator.backgroundDimens
+ GestureState.CANCELLED -> params.cancelledIndicator.backgroundDimens
+ }
+ )
)
}
@@ -628,42 +792,123 @@
private fun updateArrowState(newState: GestureState, force: Boolean = false) {
if (!force && currentState == newState) return
- if (DEBUG) Log.d(TAG, "updateArrowState $currentState -> $newState")
previousState = currentState
currentState = newState
- if (currentState == GestureState.GONE) {
- mView.cancelAlphaAnimations()
- mView.visibility = View.GONE
- } else {
- mView.visibility = View.VISIBLE
+
+ when (currentState) {
+ GestureState.CANCELLED -> {
+ backCallback.cancelBack()
+ }
+ GestureState.FLUNG,
+ GestureState.COMMITTED -> {
+ // When flung, trigger back immediately but don't fire again
+ // once state resolves to committed.
+ if (previousState != GestureState.FLUNG) backCallback.triggerBack()
+ }
+ GestureState.ENTRY,
+ GestureState.INACTIVE -> {
+ backCallback.setTriggerBack(false)
+ }
+ GestureState.ACTIVE -> {
+ backCallback.setTriggerBack(true)
+ }
+ GestureState.GONE -> { }
}
when (currentState) {
// Transitioning to GONE never animates since the arrow is (presumably) already off the
// screen
- GestureState.GONE -> updateRestingArrowDimens(animated = false, currentState)
+ GestureState.GONE -> {
+ updateRestingArrowDimens()
+ mView.isVisible = false
+ }
GestureState.ENTRY -> {
- updateYPosition(startY)
- updateRestingArrowDimens(animated = true, currentState)
+ mView.isVisible = true
+
+ updateRestingArrowDimens()
+ gestureEntryTime = SystemClock.uptimeMillis()
+ gestureInactiveOrEntryTime = SystemClock.uptimeMillis()
}
GestureState.ACTIVE -> {
- updateRestingArrowDimens(animated = true, currentState)
- // Vibrate the first time we transition to ACTIVE
- if (!hasHapticPlayed) {
- hasHapticPlayed = true
- vibrationTime = SystemClock.uptimeMillis()
- vibratorHelper.vibrate(VibrationEffect.EFFECT_TICK)
+ previousXTranslationOnActiveOffset = previousXTranslation
+ gestureActiveTime = SystemClock.uptimeMillis()
+
+ updateRestingArrowDimens()
+
+ vibratorHelper.cancel()
+ mainHandler.postDelayed(10L) {
+ vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
+ }
+
+ val startingVelocity = convertVelocityToSpringStartingVelocity(
+ valueOnFastVelocity = 0f,
+ valueOnSlowVelocity = if (previousState == GestureState.ENTRY) 2f else 4.5f
+ )
+
+ when (previousState) {
+ GestureState.ENTRY,
+ GestureState.INACTIVE -> {
+ mView.popOffEdge(startingVelocity)
+ }
+ GestureState.COMMITTED -> {
+ // if previous state was committed then this activation
+ // was due to a quick second swipe. Don't pop the arrow this time
+ }
+ else -> { }
}
}
+
GestureState.INACTIVE -> {
- updateRestingArrowDimens(animated = true, currentState)
+ gestureInactiveOrEntryTime = SystemClock.uptimeMillis()
+
+ val startingVelocity = convertVelocityToSpringStartingVelocity(
+ valueOnFastVelocity = -1.05f,
+ valueOnSlowVelocity = -1.50f
+ )
+ mView.popOffEdge(startingVelocity)
+
+ vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
+ updateRestingArrowDimens()
}
- GestureState.FLUNG -> playFlingBackAnimation()
- GestureState.COMMITTED -> playCommitBackAnimation()
- GestureState.CANCELLED -> playCancelBackAnimation()
+ GestureState.FLUNG -> {
+ mainHandler.postDelayed(POP_ON_FLING_DELAY) { mView.popScale(1.9f) }
+ playHorizontalAnimationThen(onEndSetCommittedStateListener)
+ }
+ GestureState.COMMITTED -> {
+ if (previousState == GestureState.FLUNG) {
+ playAnimationThenSetGoneEnd()
+ } else {
+ mView.popScale(3f)
+ mainHandler.postDelayed(
+ playAnimationThenSetGoneOnAlphaEnd,
+ MIN_DURATION_COMMITTED_ANIMATION
+ )
+ }
+ }
+ GestureState.CANCELLED -> {
+ val delay = max(0, MIN_DURATION_CANCELLED_ANIMATION - elapsedTimeSinceEntry)
+ playWithBackgroundWidthAnimation(onEndSetGoneStateListener, delay)
+
+ params.arrowStrokeAlphaSpring.get(0f).takeIf { it.isNewState }?.let {
+ mView.popArrowAlpha(0f, it.value)
+ }
+ mainHandler.postDelayed(10L) { vibratorHelper.cancel() }
+ }
}
}
+ private fun convertVelocityToSpringStartingVelocity(
+ valueOnFastVelocity: Float,
+ valueOnSlowVelocity: Float,
+ ): Float {
+ val factor = velocityTracker?.run {
+ computeCurrentVelocity(PX_PER_MS)
+ MathUtils.smoothStep(0f, 3f, abs(xVelocity))
+ } ?: valueOnFastVelocity
+
+ return MathUtils.lerp(valueOnFastVelocity, valueOnSlowVelocity, 1 - factor)
+ }
+
private fun scheduleFailsafe() {
if (!ENABLE_FAILSAFE) return
cancelFailsafe()
@@ -690,24 +935,24 @@
init {
if (DEBUG) mView.drawDebugInfo = { canvas ->
val debugStrings = listOf(
- "$currentState",
- "startX=$startX",
- "startY=$startY",
- "xDelta=${"%.1f".format(totalTouchDelta)}",
- "xTranslation=${"%.1f".format(previousXTranslation)}",
- "pre=${"%.0f".format(preThresholdStretchProgress(previousXTranslation) * 100)}%",
- "post=${"%.0f".format(fullScreenStretchProgress(previousXTranslation) * 100)}%"
+ "$currentState",
+ "startX=$startX",
+ "startY=$startY",
+ "xDelta=${"%.1f".format(totalTouchDelta)}",
+ "xTranslation=${"%.1f".format(previousXTranslation)}",
+ "pre=${"%.0f".format(staticThresholdProgress(previousXTranslation) * 100)}%",
+ "post=${"%.0f".format(fullScreenProgress(previousXTranslation) * 100)}%"
)
val debugPaint = Paint().apply {
color = Color.WHITE
}
val debugInfoBottom = debugStrings.size * 32f + 4f
canvas.drawRect(
- 4f,
- 4f,
- canvas.width.toFloat(),
- debugStrings.size * 32f + 4f,
- debugPaint
+ 4f,
+ 4f,
+ canvas.width.toFloat(),
+ debugStrings.size * 32f + 4f,
+ debugPaint
)
debugPaint.apply {
color = Color.BLACK
@@ -733,9 +978,71 @@
canvas.drawLine(x, debugInfoBottom, x, canvas.height.toFloat(), debugPaint)
}
- drawVerticalLine(x = params.swipeTriggerThreshold, color = Color.BLUE)
+ drawVerticalLine(x = params.staticTriggerThreshold, color = Color.BLUE)
+ drawVerticalLine(x = params.deactivationSwipeTriggerThreshold, color = Color.BLUE)
drawVerticalLine(x = startX, color = Color.GREEN)
drawVerticalLine(x = previousXTranslation, color = Color.DKGRAY)
}
}
}
+
+/**
+ * In addition to a typical step function which returns one or two
+ * values based on a threshold, `Step` also gracefully handles quick
+ * changes in input near the threshold value that would typically
+ * result in the output rapidly changing.
+ *
+ * In the context of Back arrow, the arrow's stroke opacity should
+ * always appear transparent or opaque. Using a typical Step function,
+ * this would resulting in a flickering appearance as the output would
+ * change rapidly. `Step` addresses this by moving the threshold after
+ * it is crossed so it cannot be easily crossed again with small changes
+ * in touch events.
+ */
+class Step<T>(
+ private val threshold: Float,
+ private val factor: Float = 1.1f,
+ private val postThreshold: T,
+ private val preThreshold: T
+) {
+
+ data class Value<T>(val value: T, val isNewState: Boolean)
+
+ private val lowerFactor = 2 - factor
+
+ private lateinit var startValue: Value<T>
+ private lateinit var previousValue: Value<T>
+ private var hasCrossedUpperBoundAtLeastOnce = false
+ private var progress: Float = 0f
+
+ init {
+ reset()
+ }
+
+ fun reset() {
+ hasCrossedUpperBoundAtLeastOnce = false
+ progress = 0f
+ startValue = Value(preThreshold, false)
+ previousValue = startValue
+ }
+
+ fun get(progress: Float): Value<T> {
+ this.progress = progress
+
+ val hasCrossedUpperBound = progress > threshold * factor
+ val hasCrossedLowerBound = progress > threshold * lowerFactor
+
+ return when {
+ hasCrossedUpperBound && !hasCrossedUpperBoundAtLeastOnce -> {
+ hasCrossedUpperBoundAtLeastOnce = true
+ Value(postThreshold, true)
+ }
+ hasCrossedLowerBound -> previousValue.copy(isNewState = false)
+ hasCrossedUpperBoundAtLeastOnce -> {
+ hasCrossedUpperBoundAtLeastOnce = false
+ Value(preThreshold, true)
+ }
+ else -> startValue
+ }.also { previousValue = it }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index d56537b..0c00022 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -1,52 +1,82 @@
package com.android.systemui.navigationbar.gestural
import android.content.res.Resources
+import android.util.TypedValue
+import androidx.core.animation.PathInterpolator
+import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.R
data class EdgePanelParams(private var resources: Resources) {
data class ArrowDimens(
- val length: Float = 0f,
- val height: Float = 0f
+ val length: Float = 0f,
+ val height: Float = 0f,
+ val alpha: Float = 0f,
+ var alphaSpring: SpringForce? = null,
+ val heightSpring: SpringForce? = null,
+ val lengthSpring: SpringForce? = null,
)
data class BackgroundDimens(
- val width: Float = 0f,
- val height: Float = 0f,
- val edgeCornerRadius: Float = 0f,
- val farCornerRadius: Float = 0f
+ val width: Float? = 0f,
+ val height: Float = 0f,
+ val edgeCornerRadius: Float = 0f,
+ val farCornerRadius: Float = 0f,
+ val alpha: Float = 0f,
+ val widthSpring: SpringForce? = null,
+ val heightSpring: SpringForce? = null,
+ val farCornerRadiusSpring: SpringForce? = null,
+ val edgeCornerRadiusSpring: SpringForce? = null,
+ val alphaSpring: SpringForce? = null,
)
data class BackIndicatorDimens(
- val horizontalTranslation: Float = 0f,
- val arrowDimens: ArrowDimens = ArrowDimens(),
- val backgroundDimens: BackgroundDimens = BackgroundDimens()
+ val horizontalTranslation: Float? = 0f,
+ val scale: Float = 0f,
+ val scalePivotX: Float = 0f,
+ val arrowDimens: ArrowDimens,
+ val backgroundDimens: BackgroundDimens,
+ val verticalTranslationSpring: SpringForce? = null,
+ val horizontalTranslationSpring: SpringForce? = null,
+ val scaleSpring: SpringForce? = null,
)
- var arrowThickness: Float = 0f
+ lateinit var entryIndicator: BackIndicatorDimens
private set
- var entryIndicator = BackIndicatorDimens()
+ lateinit var activeIndicator: BackIndicatorDimens
private set
- var activeIndicator = BackIndicatorDimens()
+ lateinit var cancelledIndicator: BackIndicatorDimens
private set
- var preThresholdIndicator = BackIndicatorDimens()
+ lateinit var flungIndicator: BackIndicatorDimens
private set
- var fullyStretchedIndicator = BackIndicatorDimens()
+ lateinit var committedIndicator: BackIndicatorDimens
private set
- var cancelledEdgeCornerRadius: Float = 0f
+ lateinit var preThresholdIndicator: BackIndicatorDimens
private set
- var cancelledArrowDimens = ArrowDimens()
+ lateinit var fullyStretchedIndicator: BackIndicatorDimens
+ private set
// navigation bar edge constants
var arrowPaddingEnd: Int = 0
private set
+ var arrowThickness: Float = 0f
+ private set
+ lateinit var arrowStrokeAlphaSpring: Step<SpringForce>
+ private set
+ lateinit var arrowStrokeAlphaInterpolator: Step<Float>
+ private set
// The closest to y
var minArrowYPosition: Int = 0
private set
var fingerOffset: Int = 0
private set
- var swipeTriggerThreshold: Float = 0f
+ var staticTriggerThreshold: Float = 0f
+ private set
+ var reactivationTriggerThreshold: Float = 0f
+ private set
+ var deactivationSwipeTriggerThreshold: Float = 0f
+ get() = -field
private set
var swipeProgressThreshold: Float = 0f
private set
@@ -55,6 +85,26 @@
var minDeltaForSwitch: Int = 0
private set
+ var minDragToStartAnimation: Float = 0f
+ private set
+
+ lateinit var entryWidthInterpolator: PathInterpolator
+ private set
+ lateinit var entryWidthTowardsEdgeInterpolator: PathInterpolator
+ private set
+ lateinit var activeWidthInterpolator: PathInterpolator
+ private set
+ lateinit var arrowAngleInterpolator: PathInterpolator
+ private set
+ lateinit var translationInterpolator: PathInterpolator
+ private set
+ lateinit var farCornerInterpolator: PathInterpolator
+ private set
+ lateinit var edgeCornerInterpolator: PathInterpolator
+ private set
+ lateinit var heightInterpolator: PathInterpolator
+ private set
+
init {
update(resources)
}
@@ -63,6 +113,10 @@
return resources.getDimension(id)
}
+ private fun getDimenFloat(id: Int): Float {
+ return TypedValue().run { resources.getValue(id, this, true); float }
+ }
+
private fun getPx(id: Int): Int {
return resources.getDimensionPixelSize(id)
}
@@ -73,72 +127,200 @@
arrowPaddingEnd = getPx(R.dimen.navigation_edge_panel_padding)
minArrowYPosition = getPx(R.dimen.navigation_edge_arrow_min_y)
fingerOffset = getPx(R.dimen.navigation_edge_finger_offset)
- swipeTriggerThreshold = getDimen(R.dimen.navigation_edge_action_drag_threshold)
+ staticTriggerThreshold = getDimen(R.dimen.navigation_edge_action_drag_threshold)
+ reactivationTriggerThreshold =
+ getDimen(R.dimen.navigation_edge_action_reactivation_drag_threshold)
+ deactivationSwipeTriggerThreshold =
+ getDimen(R.dimen.navigation_edge_action_deactivation_drag_threshold)
swipeProgressThreshold = getDimen(R.dimen.navigation_edge_action_progress_threshold)
minDeltaForSwitch = getPx(R.dimen.navigation_edge_minimum_x_delta_for_switch)
+ minDragToStartAnimation =
+ getDimen(R.dimen.navigation_edge_action_min_distance_to_start_animation)
+
+ entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f)
+ entryWidthTowardsEdgeInterpolator = PathInterpolator(1f, -3f, 1f, 1.2f)
+ activeWidthInterpolator = PathInterpolator(.15f, .48f, .46f, .89f)
+ arrowAngleInterpolator = entryWidthInterpolator
+ translationInterpolator = PathInterpolator(0.2f, 1.0f, 1.0f, 1.0f)
+ farCornerInterpolator = PathInterpolator(.03f, .19f, .14f, 1.09f)
+ edgeCornerInterpolator = PathInterpolator(0f, 1.11f, .85f, .84f)
+ heightInterpolator = PathInterpolator(1f, .05f, .9f, -0.29f)
+
+ val showArrowOnProgressValue = .2f
+ val showArrowOnProgressValueFactor = 1.05f
+
+ val entryActiveHorizontalTranslationSpring = createSpring(675f, 0.8f)
+ val activeCommittedArrowLengthSpring = createSpring(1500f, 0.29f)
+ val activeCommittedArrowHeightSpring = createSpring(1500f, 0.29f)
+ val flungCommittedEdgeCornerSpring = createSpring(10000f, 1f)
+ val flungCommittedFarCornerSpring = createSpring(10000f, 1f)
+ val flungCommittedWidthSpring = createSpring(10000f, 1f)
+ val flungCommittedHeightSpring = createSpring(10000f, 1f)
entryIndicator = BackIndicatorDimens(
- horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin),
- arrowDimens = ArrowDimens(
- length = getDimen(R.dimen.navigation_edge_entry_arrow_length),
- height = getDimen(R.dimen.navigation_edge_entry_arrow_height),
- ),
- backgroundDimens = BackgroundDimens(
- width = getDimen(R.dimen.navigation_edge_entry_background_width),
- height = getDimen(R.dimen.navigation_edge_entry_background_height),
- edgeCornerRadius = getDimen(R.dimen.navigation_edge_entry_edge_corners),
- farCornerRadius = getDimen(R.dimen.navigation_edge_entry_far_corners)
- )
+ horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin),
+ scale = getDimenFloat(R.dimen.navigation_edge_entry_scale),
+ scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
+ horizontalTranslationSpring = entryActiveHorizontalTranslationSpring,
+ verticalTranslationSpring = createSpring(10000f, 0.9f),
+ scaleSpring = createSpring(120f, 0.8f),
+ arrowDimens = ArrowDimens(
+ length = getDimen(R.dimen.navigation_edge_entry_arrow_length),
+ height = getDimen(R.dimen.navigation_edge_entry_arrow_height),
+ alpha = 0f,
+ alphaSpring = createSpring(200f, 1f),
+ lengthSpring = createSpring(600f, 0.4f),
+ heightSpring = createSpring(600f, 0.4f),
+ ),
+ backgroundDimens = BackgroundDimens(
+ alpha = 1f,
+ width = getDimen(R.dimen.navigation_edge_entry_background_width),
+ height = getDimen(R.dimen.navigation_edge_entry_background_height),
+ edgeCornerRadius = getDimen(R.dimen.navigation_edge_entry_edge_corners),
+ farCornerRadius = getDimen(R.dimen.navigation_edge_entry_far_corners),
+ alphaSpring = createSpring(900f, 1f),
+ widthSpring = createSpring(450f, 0.65f),
+ heightSpring = createSpring(1500f, 0.45f),
+ farCornerRadiusSpring = createSpring(300f, 0.5f),
+ edgeCornerRadiusSpring = createSpring(150f, 0.5f),
+ )
)
activeIndicator = BackIndicatorDimens(
- horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin),
- arrowDimens = ArrowDimens(
- length = getDimen(R.dimen.navigation_edge_active_arrow_length),
- height = getDimen(R.dimen.navigation_edge_active_arrow_height),
- ),
- backgroundDimens = BackgroundDimens(
- width = getDimen(R.dimen.navigation_edge_active_background_width),
- height = getDimen(R.dimen.navigation_edge_active_background_height),
- edgeCornerRadius = getDimen(R.dimen.navigation_edge_active_edge_corners),
- farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners)
-
- )
+ horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin),
+ scale = getDimenFloat(R.dimen.navigation_edge_active_scale),
+ horizontalTranslationSpring = entryActiveHorizontalTranslationSpring,
+ scaleSpring = createSpring(450f, 0.415f),
+ arrowDimens = ArrowDimens(
+ length = getDimen(R.dimen.navigation_edge_active_arrow_length),
+ height = getDimen(R.dimen.navigation_edge_active_arrow_height),
+ alpha = 1f,
+ lengthSpring = activeCommittedArrowLengthSpring,
+ heightSpring = activeCommittedArrowHeightSpring,
+ ),
+ backgroundDimens = BackgroundDimens(
+ alpha = 1f,
+ width = getDimen(R.dimen.navigation_edge_active_background_width),
+ height = getDimen(R.dimen.navigation_edge_active_background_height),
+ edgeCornerRadius = getDimen(R.dimen.navigation_edge_active_edge_corners),
+ farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners),
+ widthSpring = createSpring(375f, 0.675f),
+ heightSpring = createSpring(10000f, 1f),
+ edgeCornerRadiusSpring = createSpring(600f, 0.36f),
+ farCornerRadiusSpring = createSpring(2500f, 0.855f),
+ )
)
preThresholdIndicator = BackIndicatorDimens(
- horizontalTranslation = getDimen(R.dimen.navigation_edge_pre_threshold_margin),
- arrowDimens = ArrowDimens(
- length = entryIndicator.arrowDimens.length,
- height = entryIndicator.arrowDimens.height,
- ),
- backgroundDimens = BackgroundDimens(
- width = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
- height = getDimen(R.dimen.navigation_edge_pre_threshold_background_height),
- edgeCornerRadius = getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
- farCornerRadius = getDimen(R.dimen.navigation_edge_pre_threshold_far_corners)
- )
+ horizontalTranslation = getDimen(R.dimen.navigation_edge_pre_threshold_margin),
+ scale = getDimenFloat(R.dimen.navigation_edge_pre_threshold_scale),
+ scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
+ scaleSpring = createSpring(120f, 0.8f),
+ horizontalTranslationSpring = createSpring(6000f, 1f),
+ arrowDimens = ArrowDimens(
+ length = getDimen(R.dimen.navigation_edge_pre_threshold_arrow_length),
+ height = getDimen(R.dimen.navigation_edge_pre_threshold_arrow_height),
+ alpha = 1f,
+ lengthSpring = createSpring(100f, 0.6f),
+ heightSpring = createSpring(100f, 0.6f),
+ ),
+ backgroundDimens = BackgroundDimens(
+ alpha = 1f,
+ width = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
+ height = getDimen(R.dimen.navigation_edge_pre_threshold_background_height),
+ edgeCornerRadius =
+ getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
+ farCornerRadius =
+ getDimen(R.dimen.navigation_edge_pre_threshold_far_corners),
+ widthSpring = createSpring(200f, 0.65f),
+ heightSpring = createSpring(1500f, 0.45f),
+ farCornerRadiusSpring = createSpring(200f, 1f),
+ edgeCornerRadiusSpring = createSpring(150f, 0.5f),
+ )
+ )
+
+ committedIndicator = activeIndicator.copy(
+ horizontalTranslation = null,
+ arrowDimens = activeIndicator.arrowDimens.copy(
+ lengthSpring = activeCommittedArrowLengthSpring,
+ heightSpring = activeCommittedArrowHeightSpring,
+ ),
+ backgroundDimens = activeIndicator.backgroundDimens.copy(
+ alpha = 0f,
+ // explicitly set to null to preserve previous width upon state change
+ width = null,
+ widthSpring = flungCommittedWidthSpring,
+ heightSpring = flungCommittedHeightSpring,
+ edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring,
+ farCornerRadiusSpring = flungCommittedFarCornerSpring,
+ ),
+ scale = 0.85f,
+ scaleSpring = createSpring(650f, 1f),
+ )
+
+ flungIndicator = committedIndicator.copy(
+ arrowDimens = committedIndicator.arrowDimens.copy(
+ lengthSpring = createSpring(850f, 0.46f),
+ heightSpring = createSpring(850f, 0.46f),
+ ),
+ backgroundDimens = committedIndicator.backgroundDimens.copy(
+ widthSpring = flungCommittedWidthSpring,
+ heightSpring = flungCommittedHeightSpring,
+ edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring,
+ farCornerRadiusSpring = flungCommittedFarCornerSpring,
+ )
+ )
+
+ cancelledIndicator = entryIndicator.copy(
+ backgroundDimens = entryIndicator.backgroundDimens.copy(width = 0f)
)
fullyStretchedIndicator = BackIndicatorDimens(
- horizontalTranslation = getDimen(R.dimen.navigation_edge_stretch_margin),
- arrowDimens = ArrowDimens(
- length = getDimen(R.dimen.navigation_edge_stretched_arrow_length),
- height = getDimen(R.dimen.navigation_edge_stretched_arrow_height),
- ),
- backgroundDimens = BackgroundDimens(
- width = getDimen(R.dimen.navigation_edge_stretch_background_width),
- height = getDimen(R.dimen.navigation_edge_stretch_background_height),
- edgeCornerRadius = getDimen(R.dimen.navigation_edge_stretch_edge_corners),
- farCornerRadius = getDimen(R.dimen.navigation_edge_stretch_far_corners)
+ horizontalTranslation = getDimen(R.dimen.navigation_edge_stretch_margin),
+ scale = getDimenFloat(R.dimen.navigation_edge_stretch_scale),
+ horizontalTranslationSpring = null,
+ verticalTranslationSpring = null,
+ scaleSpring = null,
+ arrowDimens = ArrowDimens(
+ length = getDimen(R.dimen.navigation_edge_stretched_arrow_length),
+ height = getDimen(R.dimen.navigation_edge_stretched_arrow_height),
+ alpha = 1f,
+ alphaSpring = null,
+ heightSpring = null,
+ lengthSpring = null,
+ ),
+ backgroundDimens = BackgroundDimens(
+ alpha = 1f,
+ width = getDimen(R.dimen.navigation_edge_stretch_background_width),
+ height = getDimen(R.dimen.navigation_edge_stretch_background_height),
+ edgeCornerRadius = getDimen(R.dimen.navigation_edge_stretch_edge_corners),
+ farCornerRadius = getDimen(R.dimen.navigation_edge_stretch_far_corners),
+ alphaSpring = null,
+ widthSpring = null,
+ heightSpring = null,
+ edgeCornerRadiusSpring = null,
+ farCornerRadiusSpring = null,
+ )
+ )
+
+ arrowStrokeAlphaInterpolator = Step(
+ threshold = showArrowOnProgressValue,
+ factor = showArrowOnProgressValueFactor,
+ postThreshold = 1f,
+ preThreshold = 0f
+ )
+
+ entryIndicator.arrowDimens.alphaSpring?.let { alphaSpring ->
+ arrowStrokeAlphaSpring = Step(
+ threshold = showArrowOnProgressValue,
+ factor = showArrowOnProgressValueFactor,
+ postThreshold = alphaSpring,
+ preThreshold = SpringForce().setStiffness(2000f).setDampingRatio(1f)
)
- )
-
- cancelledEdgeCornerRadius = getDimen(R.dimen.navigation_edge_cancelled_edge_corners)
-
- cancelledArrowDimens = ArrowDimens(
- length = getDimen(R.dimen.navigation_edge_cancelled_arrow_length),
- height = getDimen(R.dimen.navigation_edge_cancelled_arrow_height)
- )
+ }
}
}
+
+fun createSpring(stiffness: Float, dampingRatio: Float): SpringForce {
+ return SpringForce().setStiffness(stiffness).setDampingRatio(dampingRatio)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 64a8a14..ad00069 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -171,8 +171,9 @@
getHost().collapsePanels();
};
- Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
+ final Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
mDialogLaunchAnimator, mActivityStarter, onStartRecordingClicked);
+
ActivityStarter.OnDismissAction dismissAction = () -> {
if (shouldAnimateFromView) {
mDialogLaunchAnimator.showFromView(dialog, view, new DialogCuj(
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 431b28f..acb6d96 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -38,6 +38,8 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
@@ -48,6 +50,8 @@
import javax.inject.Inject;
+import dagger.Lazy;
+
/**
* Helper class to initiate a screen recording
*/
@@ -63,6 +67,8 @@
private CountDownTimer mCountDownTimer = null;
private final Executor mMainExecutor;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final Context mContext;
+ private final FeatureFlags mFlags;
private final UserContextProvider mUserContextProvider;
private final UserTracker mUserTracker;
@@ -73,6 +79,8 @@
private CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners =
new CopyOnWriteArrayList<>();
+ private final Lazy<ScreenCaptureDevicePolicyResolver> mDevicePolicyResolver;
+
@VisibleForTesting
final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@@ -103,9 +111,15 @@
@Inject
public RecordingController(@Main Executor mainExecutor,
BroadcastDispatcher broadcastDispatcher,
+ Context context,
+ FeatureFlags flags,
UserContextProvider userContextProvider,
+ Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
UserTracker userTracker) {
mMainExecutor = mainExecutor;
+ mContext = context;
+ mFlags = flags;
+ mDevicePolicyResolver = devicePolicyResolver;
mBroadcastDispatcher = broadcastDispatcher;
mUserContextProvider = userContextProvider;
mUserTracker = userTracker;
@@ -115,14 +129,30 @@
mInteractiveBroadcastOption = options.toBundle();
}
- /** Create a dialog to show screen recording options to the user. */
+ /**
+ * MediaProjection host is SystemUI for the screen recorder, so return 'my user handle'
+ */
+ private UserHandle getHostUserHandle() {
+ return UserHandle.of(UserHandle.myUserId());
+ }
+
+ /** Create a dialog to show screen recording options to the user.
+ * If screen capturing is currently not allowed it will return a dialog
+ * that warns users about it. */
public Dialog createScreenRecordDialog(Context context, FeatureFlags flags,
DialogLaunchAnimator dialogLaunchAnimator,
ActivityStarter activityStarter,
@Nullable Runnable onStartRecordingClicked) {
+ if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)
+ && mDevicePolicyResolver.get()
+ .isScreenCaptureCompletelyDisabled(getHostUserHandle())) {
+ return new ScreenCaptureDisabledDialog(mContext);
+ }
+
return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
- ? new ScreenRecordPermissionDialog(context, this, activityStarter,
- dialogLaunchAnimator, mUserContextProvider, onStartRecordingClicked)
+ ? new ScreenRecordPermissionDialog(context, getHostUserHandle(), this,
+ activityStarter, dialogLaunchAnimator, mUserContextProvider,
+ onStartRecordingClicked)
: new ScreenRecordDialog(context, this, activityStarter,
mUserContextProvider, flags, dialogLaunchAnimator, onStartRecordingClicked);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index 68e3dcd..dd21be9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -42,6 +42,7 @@
/** Dialog to select screen recording options */
class ScreenRecordPermissionDialog(
context: Context?,
+ private val hostUserHandle: UserHandle,
private val controller: RecordingController,
private val activityStarter: ActivityStarter,
private val dialogLaunchAnimator: DialogLaunchAnimator,
@@ -79,11 +80,9 @@
CaptureTargetResultReceiver()
)
- // Send SystemUI's user handle as the host app user handle because SystemUI
- // is the 'host app' (the app that receives screen capture data)
intent.putExtra(
MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
- UserHandle.of(UserHandle.myUserId())
+ hostUserHandle
)
val animationController = dialogLaunchAnimator.createActivityLaunchController(v!!)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 2ac7f7a..6ee0a46 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -6135,6 +6135,11 @@
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
+ if (mTracking) {
+ // TODO(b/247126247) fix underlying issue. Should be ACTION_POINTER_DOWN.
+ mShadeLog.d("Don't intercept down event while already tracking");
+ return false;
+ }
mCentralSurfaces.userActivity();
mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation;
mMinExpandHeight = 0.0f;
@@ -6222,6 +6227,11 @@
"onTouch: duplicate down event detected... ignoring");
return true;
}
+ if (mTracking) {
+ // TODO(b/247126247) fix underlying issue. Should be ACTION_POINTER_DOWN.
+ mShadeLog.d("Don't handle down event while already tracking");
+ return true;
+ }
mLastTouchDownTime = event.getDownTime();
}
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 02d0f94..fe76c7d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -35,6 +35,7 @@
import android.view.ContextThemeWrapper
import android.view.View
import android.view.ViewGroup
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
@@ -83,6 +84,7 @@
private val statusBarStateController: StatusBarStateController,
private val deviceProvisionedController: DeviceProvisionedController,
private val bypassController: KeyguardBypassController,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val execution: Execution,
@Main private val uiExecutor: Executor,
@Background private val bgExecutor: Executor,
@@ -176,10 +178,10 @@
}
if (weatherTarget != null) {
val weatherData = Weather.fromBundle(weatherTarget.baseAction.extras)
+ keyguardUpdateMonitor.sendWeatherData(weatherData)
}
}
-
private val userTrackerCallback = object : UserTracker.Callback {
override fun onUserChanged(newUser: Int, userContext: Context) {
execution.assertIsMainThread()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 5f6a5cb..26f97de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -63,7 +63,7 @@
* are not.
*/
public class NotificationLogger implements StateListener {
- private static final String TAG = "NotificationLogger";
+ static final String TAG = "NotificationLogger";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
/** The minimum delay in ms between reports of notification visibility. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
index ec8501a..cc1103d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
@@ -18,6 +18,7 @@
package com.android.systemui.statusbar.notification.logging
import android.app.StatsManager
+import android.util.Log
import android.util.StatsEvent
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -25,6 +26,7 @@
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.util.traceSection
+import java.lang.Exception
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlin.math.roundToInt
@@ -82,43 +84,56 @@
return StatsManager.PULL_SKIP
}
- // Notifications can only be retrieved on the main thread, so switch to that thread.
- val notifications = getAllNotificationsOnMainThread()
- val notificationMemoryUse =
- NotificationMemoryMeter.notificationMemoryUse(notifications)
- .sortedWith(
- compareBy(
- { it.packageName },
- { it.objectUsage.style },
- { it.notificationKey }
+ try {
+ // Notifications can only be retrieved on the main thread, so switch to that thread.
+ val notifications = getAllNotificationsOnMainThread()
+ val notificationMemoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(notifications)
+ .sortedWith(
+ compareBy(
+ { it.packageName },
+ { it.objectUsage.style },
+ { it.notificationKey }
+ )
+ )
+ val usageData = aggregateMemoryUsageData(notificationMemoryUse)
+ usageData.forEach { (_, use) ->
+ data.add(
+ SysUiStatsLog.buildStatsEvent(
+ SysUiStatsLog.NOTIFICATION_MEMORY_USE,
+ use.uid,
+ use.style,
+ use.count,
+ use.countWithInflatedViews,
+ toKb(use.smallIconObject),
+ use.smallIconBitmapCount,
+ toKb(use.largeIconObject),
+ use.largeIconBitmapCount,
+ toKb(use.bigPictureObject),
+ use.bigPictureBitmapCount,
+ toKb(use.extras),
+ toKb(use.extenders),
+ toKb(use.smallIconViews),
+ toKb(use.largeIconViews),
+ toKb(use.systemIconViews),
+ toKb(use.styleViews),
+ toKb(use.customViews),
+ toKb(use.softwareBitmaps),
+ use.seenCount
)
)
- val usageData = aggregateMemoryUsageData(notificationMemoryUse)
- usageData.forEach { (_, use) ->
- data.add(
- SysUiStatsLog.buildStatsEvent(
- SysUiStatsLog.NOTIFICATION_MEMORY_USE,
- use.uid,
- use.style,
- use.count,
- use.countWithInflatedViews,
- toKb(use.smallIconObject),
- use.smallIconBitmapCount,
- toKb(use.largeIconObject),
- use.largeIconBitmapCount,
- toKb(use.bigPictureObject),
- use.bigPictureBitmapCount,
- toKb(use.extras),
- toKb(use.extenders),
- toKb(use.smallIconViews),
- toKb(use.largeIconViews),
- toKb(use.systemIconViews),
- toKb(use.styleViews),
- toKb(use.customViews),
- toKb(use.softwareBitmaps),
- use.seenCount
- )
- )
+ }
+ } catch (e: InterruptedException) {
+ // This can happen if the device is sleeping or view walking takes too long.
+ // The statsd collector will interrupt the thread and we need to handle it
+ // gracefully.
+ Log.w(NotificationLogger.TAG, "Timed out when measuring notification memory.", e)
+ return@traceSection StatsManager.PULL_SKIP
+ } catch (e: Exception) {
+ // Error while collecting data, this should not crash prod SysUI. Just
+ // log WTF and move on.
+ Log.wtf(NotificationLogger.TAG, "Failed to measure notification memory.", e)
+ return@traceSection StatsManager.PULL_SKIP
}
return StatsManager.PULL_SUCCESS
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
index 2d04211..6491223 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
@@ -184,19 +184,21 @@
private fun computeDrawableUse(drawable: Drawable, seenObjects: HashSet<Int>): Int =
when (drawable) {
is BitmapDrawable -> {
- val ref = System.identityHashCode(drawable.bitmap)
- if (seenObjects.contains(ref)) {
- 0
- } else {
- seenObjects.add(ref)
- drawable.bitmap.allocationByteCount
- }
+ drawable.bitmap?.let {
+ val ref = System.identityHashCode(it)
+ if (seenObjects.contains(ref)) {
+ 0
+ } else {
+ seenObjects.add(ref)
+ it.allocationByteCount
+ }
+ } ?: 0
}
else -> 0
}
private fun isDrawableSoftwareBitmap(drawable: Drawable) =
- drawable is BitmapDrawable && drawable.bitmap.config != Bitmap.Config.HARDWARE
+ drawable is BitmapDrawable && drawable.bitmap?.config != Bitmap.Config.HARDWARE
private fun identifierForView(view: View) =
if (view.id == View.NO_ID) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 01af486..c163a89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -89,14 +89,19 @@
private float mPanelExpansion;
/**
- * Burn-in prevention x translation.
+ * Max burn-in prevention x translation.
*/
- private int mBurnInPreventionOffsetX;
+ private int mMaxBurnInPreventionOffsetX;
/**
- * Burn-in prevention y translation for clock layouts.
+ * Max burn-in prevention y translation for clock layouts.
*/
- private int mBurnInPreventionOffsetYClock;
+ private int mMaxBurnInPreventionOffsetYClock;
+
+ /**
+ * Current burn-in prevention y translation.
+ */
+ private float mCurrentBurnInOffsetY;
/**
* Doze/AOD transition amount.
@@ -155,9 +160,9 @@
mContainerTopPadding =
res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
- mBurnInPreventionOffsetX = res.getDimensionPixelSize(
+ mMaxBurnInPreventionOffsetX = res.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_x);
- mBurnInPreventionOffsetYClock = res.getDimensionPixelSize(
+ mMaxBurnInPreventionOffsetYClock = res.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_y_clock);
}
@@ -215,7 +220,10 @@
if (mBypassEnabled) {
return (int) (mUnlockedStackScrollerPadding + mOverStretchAmount);
} else if (mIsSplitShade) {
- return clockYPosition - mSplitShadeTopNotificationsMargin + mUserSwitchHeight;
+ // mCurrentBurnInOffsetY is subtracted to make notifications not follow clock adjustment
+ // for burn-in. It can make pulsing notification go too high and it will get clipped
+ return clockYPosition - mSplitShadeTopNotificationsMargin + mUserSwitchHeight
+ - (int) mCurrentBurnInOffsetY;
} else {
return clockYPosition + mKeyguardStatusHeight;
}
@@ -255,11 +263,11 @@
// This will keep the clock at the top but out of the cutout area
float shift = 0;
- if (clockY - mBurnInPreventionOffsetYClock < mCutoutTopInset) {
- shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYClock);
+ if (clockY - mMaxBurnInPreventionOffsetYClock < mCutoutTopInset) {
+ shift = mCutoutTopInset - (clockY - mMaxBurnInPreventionOffsetYClock);
}
- int burnInPreventionOffsetY = mBurnInPreventionOffsetYClock; // requested offset
+ int burnInPreventionOffsetY = mMaxBurnInPreventionOffsetYClock; // requested offset
final boolean hasUdfps = mUdfpsTop > -1;
if (hasUdfps && !mIsClockTopAligned) {
// ensure clock doesn't overlap with the udfps icon
@@ -267,8 +275,8 @@
// sometimes the clock textView extends beyond udfps, so let's just use the
// space above the KeyguardStatusView/clock as our burn-in offset
burnInPreventionOffsetY = (int) (clockY - mCutoutTopInset) / 2;
- if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
- burnInPreventionOffsetY = mBurnInPreventionOffsetYClock;
+ if (mMaxBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
+ burnInPreventionOffsetY = mMaxBurnInPreventionOffsetYClock;
}
shift = -burnInPreventionOffsetY;
} else {
@@ -276,16 +284,18 @@
float lowerSpace = mUdfpsTop - mClockBottom;
// center the burn-in offset within the upper + lower space
burnInPreventionOffsetY = (int) (lowerSpace + upperSpace) / 2;
- if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
- burnInPreventionOffsetY = mBurnInPreventionOffsetYClock;
+ if (mMaxBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
+ burnInPreventionOffsetY = mMaxBurnInPreventionOffsetYClock;
}
shift = (lowerSpace - upperSpace) / 2;
}
}
+ float fullyDarkBurnInOffset = burnInPreventionOffsetY(burnInPreventionOffsetY);
float clockYDark = clockY
- + burnInPreventionOffsetY(burnInPreventionOffsetY)
+ + fullyDarkBurnInOffset
+ shift;
+ mCurrentBurnInOffsetY = MathUtils.lerp(0, fullyDarkBurnInOffset, darkAmount);
return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
}
@@ -325,7 +335,7 @@
}
private float burnInPreventionOffsetX() {
- return getBurnInOffset(mBurnInPreventionOffsetX, true /* xAxis */);
+ return getBurnInOffset(mMaxBurnInPreventionOffsetX, true /* xAxis */);
}
public static class Result {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index d912793..082c8cc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -134,4 +134,10 @@
keyguardPasswordViewController.startAppearAnimation()
verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
}
+
+ @Test
+ fun testMessageIsSetWhenReset() {
+ keyguardPasswordViewController.resetState()
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index b742100..0881e61 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -120,4 +120,10 @@
public void testGetInitialMessageResId() {
assertThat(mKeyguardPinViewController.getInitialMessageResId()).isNotEqualTo(0);
}
+
+ @Test
+ public void testMessageIsSetWhenReset() {
+ mKeyguardPinViewController.resetState();
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 075ef9d..0d65f12 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -583,6 +583,8 @@
verify(mKeyguardSecurityViewFlipperController).clearViews();
verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
any(KeyguardSecurityCallback.class));
+ verify(mView).reset();
+ verify(mKeyguardSecurityViewFlipperController).reset();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index fc11148..4cf5a4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -34,8 +34,10 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -104,8 +106,12 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.List;
@@ -119,7 +125,7 @@
private WindowManager mWindowManager;
private DisplayManager mDisplayManager;
private SecureSettings mSecureSettings;
- private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ private FakeExecutor mExecutor;
private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
private FakeThreadFactory mThreadFactory;
private ArrayList<DecorProvider> mPrivacyDecorProviders;
@@ -161,6 +167,8 @@
private PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener;
@Mock
private CutoutDecorProviderFactory mCutoutFactory;
+ @Captor
+ private ArgumentCaptor<AuthController.Callback> mAuthControllerCallback;
private List<DecorProvider> mMockCutoutList;
@Before
@@ -169,6 +177,7 @@
Handler mainHandler = new Handler(TestableLooper.get(this).getLooper());
mSecureSettings = new FakeSettings();
+ mExecutor = new FakeExecutor(new FakeSystemClock());
mThreadFactory = new FakeThreadFactory(mExecutor);
mThreadFactory.setHandler(mainHandler);
@@ -1166,6 +1175,44 @@
}
@Test
+ public void faceSensorLocationChangesReloadsFaceScanningOverlay() {
+ mFaceScanningProviders = new ArrayList<>();
+ mFaceScanningProviders.add(mFaceScanningDecorProvider);
+ when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders);
+ when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(true);
+ ScreenDecorations screenDecorations = new ScreenDecorations(mContext, mExecutor,
+ mSecureSettings, mTunerService, mUserTracker, mDisplayTracker, mDotViewController,
+ mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController);
+ screenDecorations.start();
+ verify(mAuthController).addCallback(mAuthControllerCallback.capture());
+ when(mContext.getDisplay()).thenReturn(mDisplay);
+ when(mDisplay.getDisplayInfo(any())).thenAnswer(new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ DisplayInfo displayInfo = invocation.getArgument(0);
+ int modeId = 1;
+ displayInfo.modeId = modeId;
+ displayInfo.supportedModes = new Display.Mode[]{new Display.Mode(modeId, 1024, 1024,
+ 90)};
+ return false;
+ }
+ });
+ mExecutor.runAllReady();
+ clearInvocations(mFaceScanningDecorProvider);
+
+ AuthController.Callback callback = mAuthControllerCallback.getValue();
+ callback.onFaceSensorLocationChanged();
+ mExecutor.runAllReady();
+
+ verify(mFaceScanningDecorProvider).onReloadResAndMeasure(any(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ any());
+ }
+
+ @Test
public void testPrivacyDotShowingListenerWorkWellWithNullParameter() {
mPrivacyDotShowingListener.onPrivacyDotShown(null);
mPrivacyDotShowingListener.onPrivacyDotHidden(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 5afe49e..0d00e8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -85,6 +85,7 @@
import com.android.internal.R;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
+import com.android.settingslib.udfps.UdfpsUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
@@ -167,6 +168,8 @@
private BiometricPromptCredentialInteractor mBiometricPromptCredentialInteractor;
@Mock
private CredentialViewModel mCredentialViewModel;
+ @Mock
+ private UdfpsUtils mUdfpsUtils;
@Captor
private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
@@ -958,7 +961,7 @@
mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger,
mLogContextInteractor, () -> mBiometricPromptCredentialInteractor,
() -> mCredentialViewModel, mInteractionJankMonitor, mHandler,
- mBackgroundExecutor, mVibratorHelper);
+ mBackgroundExecutor, mVibratorHelper, mUdfpsUtils);
}
@Override
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 36ed6d5..9866163 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -38,6 +38,8 @@
import android.view.accessibility.AccessibilityManager
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.settingslib.udfps.UdfpsOverlayParams
+import com.android.settingslib.udfps.UdfpsUtils
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
@@ -110,6 +112,7 @@
@Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+ @Mock private lateinit var udfpsUtils: UdfpsUtils
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -144,7 +147,7 @@
configurationController, keyguardStateController, unlockedScreenOffAnimationController,
udfpsDisplayMode, secureSettings, REQUEST_ID, reason,
controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
- primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable
+ primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils
)
block()
}
@@ -400,109 +403,6 @@
assertThat(controllerOverlay.matchesRequestId(REQUEST_ID)).isTrue()
assertThat(controllerOverlay.matchesRequestId(REQUEST_ID + 1)).isFalse()
}
-
- @Test
- fun testTouchOutsideAreaNoRotation() = withReason(REASON_ENROLL_ENROLLING) {
- val touchHints =
- context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
- val rotation = Surface.ROTATION_0
- // touch at 0 degrees
- assertThat(
- controllerOverlay.onTouchOutsideOfSensorAreaImpl(
- 0.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
- )
- ).isEqualTo(touchHints[0])
- // touch at 90 degrees
- assertThat(
- controllerOverlay.onTouchOutsideOfSensorAreaImpl(
- 0.0f /* x */, -1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
- )
- ).isEqualTo(touchHints[1])
- // touch at 180 degrees
- assertThat(
- controllerOverlay.onTouchOutsideOfSensorAreaImpl(
- -1.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
- )
- ).isEqualTo(touchHints[2])
- // touch at 270 degrees
- assertThat(
- controllerOverlay.onTouchOutsideOfSensorAreaImpl(
- 0.0f /* x */, 1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
- )
- ).isEqualTo(touchHints[3])
- }
-
- fun testTouchOutsideAreaNoRotation90Degrees() = withReason(REASON_ENROLL_ENROLLING) {
- val touchHints =
- context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
- val rotation = Surface.ROTATION_90
- // touch at 0 degrees -> 90 degrees
- assertThat(
- controllerOverlay.onTouchOutsideOfSensorAreaImpl(
- 0.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
- )
- ).isEqualTo(touchHints[1])
- // touch at 90 degrees -> 180 degrees
- assertThat(
- controllerOverlay.onTouchOutsideOfSensorAreaImpl(
- 0.0f /* x */, -1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
- )
- ).isEqualTo(touchHints[2])
- // touch at 180 degrees -> 270 degrees
- assertThat(
- controllerOverlay.onTouchOutsideOfSensorAreaImpl(
- -1.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
- )
- ).isEqualTo(touchHints[3])
- // touch at 270 degrees -> 0 degrees
- assertThat(
- controllerOverlay.onTouchOutsideOfSensorAreaImpl(
- 0.0f /* x */, 1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
- )
- ).isEqualTo(touchHints[0])
- }
-
- fun testTouchOutsideAreaNoRotation270Degrees() = withReason(REASON_ENROLL_ENROLLING) {
- val touchHints =
- context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
- val rotation = Surface.ROTATION_270
- // touch at 0 degrees -> 270 degrees
- assertThat(
- controllerOverlay.onTouchOutsideOfSensorAreaImpl(
- 0.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
- )
- ).isEqualTo(touchHints[3])
- // touch at 90 degrees -> 0 degrees
- assertThat(
- controllerOverlay.onTouchOutsideOfSensorAreaImpl(
- 0.0f /* x */, -1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
- )
- ).isEqualTo(touchHints[0])
- // touch at 180 degrees -> 90 degrees
- assertThat(
- controllerOverlay.onTouchOutsideOfSensorAreaImpl(
- -1.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
- )
- ).isEqualTo(touchHints[1])
- // touch at 270 degrees -> 180 degrees
- assertThat(
- controllerOverlay.onTouchOutsideOfSensorAreaImpl(
- 0.0f /* x */, 1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
- )
- ).isEqualTo(touchHints[2])
- }
}
private class EnrollListener(
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 dd7082a..17c262d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -71,6 +71,8 @@
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.settingslib.udfps.UdfpsOverlayParams;
+import com.android.settingslib.udfps.UdfpsUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -230,10 +232,12 @@
private ScreenLifecycle.Observer mScreenObserver;
private FingerprintSensorPropertiesInternal mOpticalProps;
private FingerprintSensorPropertiesInternal mUltrasonicProps;
+ private UdfpsUtils mUdfpsUtils;
@Before
public void setUp() {
Execution execution = new FakeExecution();
+ mUdfpsUtils = new UdfpsUtils();
when(mLayoutInflater.inflate(R.layout.udfps_view, null, false))
.thenReturn(mUdfpsView);
@@ -305,7 +309,7 @@
mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
- mAlternateBouncerInteractor, mSecureSettings);
+ mAlternateBouncerInteractor, mSecureSettings, mUdfpsUtils);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index 44fa4eb..07b4a64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -25,6 +25,7 @@
import android.view.LayoutInflater
import android.view.Surface
import androidx.test.filters.SmallTest
+import com.android.settingslib.udfps.UdfpsOverlayParams
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
index 8e20303..c40fd4fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
@@ -23,8 +23,8 @@
import android.view.Surface
import android.view.Surface.Rotation
import androidx.test.filters.SmallTest
+import com.android.settingslib.udfps.UdfpsOverlayParams
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.UdfpsOverlayParams
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 8127ccc..6e6833d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -16,13 +16,15 @@
package com.android.systemui.screenrecord;
+import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.app.Dialog;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Looper;
@@ -31,7 +33,13 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -61,8 +69,15 @@
@Mock
private UserContextProvider mUserContextProvider;
@Mock
+ private ScreenCaptureDevicePolicyResolver mDevicePolicyResolver;
+ @Mock
+ private DialogLaunchAnimator mDialogLaunchAnimator;
+ @Mock
+ private ActivityStarter mActivityStarter;
+ @Mock
private UserTracker mUserTracker;
+ private FakeFeatureFlags mFeatureFlags;
private RecordingController mController;
private static final int USER_ID = 10;
@@ -70,8 +85,9 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mController = new RecordingController(mMainExecutor, mBroadcastDispatcher,
- mUserContextProvider, mUserTracker);
+ mFeatureFlags = new FakeFeatureFlags();
+ mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, mContext,
+ mFeatureFlags, mUserContextProvider, () -> mDevicePolicyResolver, mUserTracker);
mController.addCallback(mCallback);
}
@@ -190,4 +206,67 @@
verify(mCallback).onRecordingEnd();
assertFalse(mController.isRecording());
}
+
+ @Test
+ public void testPoliciesFlagDisabled_screenCapturingNotAllowed_returnsNullDevicePolicyDialog() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
+ when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
+
+ Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
+ mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+ assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class);
+ }
+
+ @Test
+ public void testPartialScreenSharingDisabled_returnsLegacyDialog() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, false);
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
+
+ Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
+ mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+ assertThat(dialog).isInstanceOf(ScreenRecordDialog.class);
+ }
+
+ @Test
+ public void testPoliciesFlagEnabled_screenCapturingNotAllowed_returnsDevicePolicyDialog() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
+ when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
+
+ Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
+ mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+ assertThat(dialog).isInstanceOf(ScreenCaptureDisabledDialog.class);
+ }
+
+ @Test
+ public void testPoliciesFlagEnabled_screenCapturingAllowed_returnsNullDevicePolicyDialog() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
+ when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
+
+ Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
+ mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+ assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index 0aa3621..5b094c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.screenrecord
+import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -59,6 +60,7 @@
dialog =
ScreenRecordPermissionDialog(
context,
+ UserHandle.of(0),
controller,
starter,
dialogLaunchAnimator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 0a576de..9c69a6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -32,6 +32,7 @@
import android.view.View
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -104,6 +105,9 @@
private lateinit var keyguardBypassController: KeyguardBypassController
@Mock
+ private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+ @Mock
private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock
@@ -223,6 +227,7 @@
statusBarStateController,
deviceProvisionedController,
keyguardBypassController,
+ keyguardUpdateMonitor,
execution,
executor,
bgExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
index 33b94e3..bd03903 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.lang.RuntimeException
import kotlinx.coroutines.Dispatchers
import org.junit.Before
import org.junit.Test
@@ -113,6 +114,24 @@
assertThat(data).hasSize(2)
}
+ @Test
+ fun onPullAtom_throwsInterruptedException_failsGracefully() {
+ val pipeline: NotifPipeline = mock()
+ whenever(pipeline.allNotifs).thenAnswer { throw InterruptedException("Timeout") }
+ val logger = NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
+ assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
+ .isEqualTo(StatsManager.PULL_SKIP)
+ }
+
+ @Test
+ fun onPullAtom_throwsRuntimeException_failsGracefully() {
+ val pipeline: NotifPipeline = mock()
+ whenever(pipeline.allNotifs).thenThrow(RuntimeException("Something broke!"))
+ val logger = NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
+ assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
+ .isEqualTo(StatsManager.PULL_SKIP)
+ }
+
private fun createLoggerWithNotifications(
notifications: List<Notification>
): NotificationMemoryLogger {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index ed3f710..7e69efa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -264,6 +264,19 @@
}
@Test
+ public void notifPositionAlignedWithClockAndBurnInOffsetInSplitShadeMode() {
+ setSplitShadeTopMargin(100); // this makes clock to be at 100
+ givenAOD();
+ mIsSplitShade = true;
+ givenMaxBurnInOffset(100);
+ givenHighestBurnInOffset(); // this makes clock to be at 200
+ // WHEN the position algorithm is run
+ positionClock();
+ // THEN the notif padding adjusts for burn-in offset: clock position - burn-in offset
+ assertThat(mClockPosition.stackScrollerPadding).isEqualTo(100);
+ }
+
+ @Test
public void clockPositionedDependingOnMarginInSplitShade() {
setSplitShadeTopMargin(400);
givenLockScreen();
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 37069dc..595cdec 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -52,6 +52,7 @@
import android.view.animation.DecelerateInterpolator;
import com.android.internal.R;
+import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -1157,11 +1158,14 @@
}
/**
- * Persists the default display magnification scale to the current user's settings.
+ * Persists the default display magnification scale to the current user's settings
+ * <strong>if scale is >= {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}</strong>.
+ * We assume if the scale is < {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}, there
+ * will be no obvious magnification effect.
*/
public void persistScale(int displayId) {
final float scale = getScale(Display.DEFAULT_DISPLAY);
- if (scale < 2.0f) {
+ if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
return;
}
mScaleProvider.putScale(scale, displayId);
@@ -1176,7 +1180,8 @@
*/
public float getPersistedScale(int displayId) {
return MathUtils.constrain(mScaleProvider.getScale(displayId),
- 2.0f, MagnificationScaleProvider.MAX_SCALE);
+ MagnificationConstants.PERSISTED_SCALE_MIN_VALUE,
+ MagnificationScaleProvider.MAX_SCALE);
}
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 6bf37a1..9fc9d57 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -992,9 +992,8 @@
mFullScreenMagnificationController.getPersistedScale(mDisplayId),
MIN_SCALE, MAX_SCALE);
- final float scale = MathUtils.constrain(Math.max(currentScale + 1.0f, persistedScale),
- MIN_SCALE, MAX_SCALE);
-
+ final boolean isActivated = mFullScreenMagnificationController.isActivated(mDisplayId);
+ final float scale = isActivated ? (currentScale + 1.0f) : persistedScale;
zoomToScale(scale, centerX, centerY);
}
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 2d5f894..d9391f4 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -46,6 +46,7 @@
import android.view.accessibility.IWindowMagnificationConnectionCallback;
import android.view.accessibility.MagnificationAnimationCallback;
+import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -716,17 +717,20 @@
*/
float getPersistedScale(int displayId) {
return MathUtils.constrain(mScaleProvider.getScale(displayId),
- 2.0f, MagnificationScaleProvider.MAX_SCALE);
+ MagnificationConstants.PERSISTED_SCALE_MIN_VALUE,
+ MagnificationScaleProvider.MAX_SCALE);
}
/**
* Persists the default display magnification scale to the current user's settings
- * <strong>if scale is >= 2.0</strong>. Only the
- * value of the default display is persisted in user's settings.
+ * <strong>if scale is >= {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}</strong>.
+ * We assume if the scale is < {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}, there
+ * will be no obvious magnification effect.
+ * Only the value of the default display is persisted in user's settings.
*/
void persistScale(int displayId) {
float scale = getScale(displayId);
- if (scale < 2.0f) {
+ if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
return;
}
mScaleProvider.putScale(scale, displayId);
diff --git a/services/api/current.txt b/services/api/current.txt
index 70ee3b8..a4deed3 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -227,8 +227,9 @@
package com.android.server.security {
- public final class FileIntegrityLocal {
- method public static void setUpFsVerity(@NonNull String) throws java.io.IOException;
+ public final class FileIntegrity {
+ method public static void setUpFsVerity(@NonNull java.io.File) throws java.io.IOException;
+ method public static void setUpFsVerity(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
}
}
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 5f1da7b..590f472 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -315,7 +315,12 @@
event.mFillRequestSentTimestampMs,
event.mFillResponseReceivedTimestampMs,
event.mSuggestionSentTimestampMs,
- event.mSuggestionPresentedTimestampMs);
+ event.mSuggestionPresentedTimestampMs,
+ //TODO(b/265051751): add new framework logging.
+ /* selected_dataset_id= */ 0,
+ /* dialog_dismissed= */ false,
+ /* negative_cta_button_clicked= */ false,
+ /* positive_cta_button_clicked= */ false);
mEventInternal = Optional.empty();
}
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 5eb0db1..6fd6afe 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -182,7 +182,6 @@
public void onStart() {
// Do init on a separate thread, will join in PHASE_ACTIVITY_MANAGER_READY
SystemServerInitThreadPool.submit(() -> {
- mAllowedUid = getAllowedUid();
enforceChecksumValidity();
formatIfOemUnlockEnabled();
publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
@@ -202,6 +201,8 @@
Thread.currentThread().interrupt();
throw new IllegalStateException("Service " + TAG + " init interrupted", e);
}
+ // The user responsible for FRP should exist by now.
+ mAllowedUid = getAllowedUid();
LocalServices.addService(PersistentDataBlockManagerInternal.class, mInternalService);
}
super.onBootPhase(phase);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 2689193..913f151 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -742,9 +742,6 @@
"compactApp " + app.mOptRecord.getReqCompactSource().name() + " "
+ app.mOptRecord.getReqCompactProfile().name() + " " + processName);
}
- Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
- "compactApp " + app.mOptRecord.getReqCompactSource().name() + " "
- + app.mOptRecord.getReqCompactProfile().name() + " " + processName);
app.mOptRecord.setHasPendingCompact(true);
app.mOptRecord.setForceCompact(force);
mPendingCompactionProcesses.add(app);
@@ -1820,7 +1817,8 @@
try {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"Compact " + resolvedAction.name() + ": " + name
- + " lastOomAdjReason: " + oomAdjReason);
+ + " lastOomAdjReason: " + oomAdjReason
+ + " source: " + compactSource.name());
long zramUsedKbBefore = getUsedZramMemory();
long startCpuTime = threadCpuTimeNs();
mProcessDependencies.performCompaction(resolvedAction, pid);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index f22624c..12784bf 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -80,6 +80,7 @@
@VisibleForTesting
static final String[] sDeviceConfigScopes = new String[] {
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+ DeviceConfig.NAMESPACE_CAMERA_NATIVE,
DeviceConfig.NAMESPACE_CONFIGURATION,
DeviceConfig.NAMESPACE_CONNECTIVITY,
DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b2e4740..0a019f3 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2246,7 +2246,7 @@
// If there is no challenge set, dismiss the keyguard right away
if (isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId)) {
// Wait until the keyguard is dismissed to unfreeze
- mInjector.dismissKeyguard(runnable, "User Switch");
+ mInjector.dismissKeyguard(runnable);
} else {
runnable.run();
}
@@ -3714,7 +3714,7 @@
return IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
}
- protected void dismissKeyguard(Runnable runnable, String reason) {
+ protected void dismissKeyguard(Runnable runnable) {
final AtomicBoolean isFirst = new AtomicBoolean(true);
final Runnable runOnce = () -> {
if (isFirst.getAndSet(false)) {
@@ -3738,7 +3738,7 @@
public void onDismissCancelled() throws RemoteException {
mHandler.post(runOnce);
}
- }, reason);
+ }, /* message= */ null);
}
boolean isUsersOnSecondaryDisplaysEnabled() {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d888c81..c01424d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3647,11 +3647,12 @@
synchronized (VolumeStreamState.class) {
List<Integer> streamsToMute = new ArrayList<>();
for (int stream = 0; stream < mStreamStates.length; stream++) {
- if (streamAlias == mStreamVolumeAlias[stream]) {
+ VolumeStreamState vss = mStreamStates[stream];
+ if (streamAlias == mStreamVolumeAlias[stream] && vss.isMutable()) {
if (!(readCameraSoundForced()
- && (mStreamStates[stream].getStreamType()
+ && (vss.getStreamType()
== AudioSystem.STREAM_SYSTEM_ENFORCED))) {
- boolean changed = mStreamStates[stream].mute(state, /* apply= */ false);
+ boolean changed = vss.mute(state, /* apply= */ false);
if (changed) {
streamsToMute.add(stream);
}
@@ -5329,7 +5330,8 @@
if (!shouldMute) {
// unmute
// ring and notifications volume should never be 0 when not silenced
- if (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) {
+ if (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING
+ || mStreamVolumeAlias[streamType] == AudioSystem.STREAM_NOTIFICATION) {
synchronized (VolumeStreamState.class) {
final VolumeStreamState vss = mStreamStates[streamType];
for (int i = 0; i < vss.mIndexMap.size(); i++) {
@@ -6060,6 +6062,8 @@
}
}
+ readVolumeGroupsSettings(userSwitch);
+
// apply new ringer mode before checking volume for alias streams so that streams
// muted by ringer mode have the correct volume
setRingerModeInt(getRingerModeInternal(), false);
@@ -6071,8 +6075,6 @@
mSoundDoseHelper.restoreMusicActiveMs();
mSoundDoseHelper.enforceSafeMediaVolumeIfActive(TAG);
- readVolumeGroupsSettings(userSwitch);
-
if (DEBUG_VOL) {
Log.d(TAG, "Restoring device volume behavior");
}
@@ -8478,9 +8480,10 @@
}
mVolumeGroupState.updateVolumeIndex(groupIndex, device);
// Only propage mute of stream when applicable
- if (mIndexMin == 0 || isCallStream(mStreamType)) {
+ if (isMutable()) {
// For call stream, align mute only when muted, not when index is set to 0
- mVolumeGroupState.mute(forceMuteState ? mIsMuted : groupIndex == 0);
+ mVolumeGroupState.mute(
+ forceMuteState ? mIsMuted : groupIndex == 0 || mIsMuted);
}
}
}
@@ -8529,6 +8532,12 @@
return mIsMuted || mIsMutedInternally;
}
+
+ private boolean isMutable() {
+ return isStreamAffectedByMute(mStreamType)
+ && (mIndexMin == 0 || isCallStream(mStreamType));
+ }
+
/**
* Mute/unmute the stream
* @param state the new mute state
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index a921a54..7026529 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -22,6 +22,7 @@
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;
@@ -114,6 +115,7 @@
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);
@@ -124,7 +126,8 @@
d.isDefaultDisplay(),
d.isEnabled(),
mIdProducer,
- d.getBrightnessThrottlingMapId());
+ d.getBrightnessThrottlingMapId(),
+ leadDisplayId);
if (FRONT_STRING.equals(d.getPosition())) {
display.setPosition(POSITION_FRONT);
@@ -133,6 +136,7 @@
} else {
display.setPosition(POSITION_UNKNOWN);
}
+ display.setRefreshRateZoneId(d.getRefreshRateZoneId());
}
}
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 2af995b..a107f33 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -33,6 +33,7 @@
import android.util.Slog;
import android.util.Spline;
import android.view.DisplayAddress;
+import android.view.SurfaceControl;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -53,6 +54,7 @@
import com.android.server.display.config.Point;
import com.android.server.display.config.RefreshRateConfigs;
import com.android.server.display.config.RefreshRateRange;
+import com.android.server.display.config.RefreshRateZone;
import com.android.server.display.config.SdrHdrRatioMap;
import com.android.server.display.config.SdrHdrRatioPoint;
import com.android.server.display.config.SensorDetails;
@@ -503,10 +505,10 @@
/**
* Array of light sensor lux values to define our levels for auto backlight
* brightness support.
-
+ *
* The N + 1 entries of this array define N control points defined in mBrightnessLevelsNits,
* with first value always being 0 lux
-
+ *
* The control points must be strictly increasing. Each control point
* corresponds to an entry in the brightness backlight values arrays.
* For example, if lux == level[1] (second element of the levels array)
@@ -515,7 +517,6 @@
*
* Spline interpolation is used to determine the auto-brightness
* backlight values for lux levels between these control points.
- *
*/
private float[] mBrightnessLevelsLux;
@@ -619,6 +620,10 @@
*/
private int mDefaultLowBlockingZoneRefreshRate = DEFAULT_LOW_REFRESH_RATE;
+ // Refresh rate profiles, currently only for concurrent mode profile and controlled by Layout
+ private final Map<String, SurfaceControl.RefreshRateRange> mRefreshRateZoneProfiles =
+ new HashMap<>();
+
/**
* The display uses different gamma curves for different refresh rates. It's hard for panel
* vendors to tune the curves to have exact same brightness for different refresh rate. So
@@ -1354,6 +1359,23 @@
}
/**
+ * @return Refresh rate range for specific profile id or null
+ */
+ @Nullable
+ public SurfaceControl.RefreshRateRange getRefreshRange(@Nullable String id) {
+ if (TextUtils.isEmpty(id)) {
+ return null;
+ }
+ return mRefreshRateZoneProfiles.get(id);
+ }
+
+ @NonNull
+ @VisibleForTesting
+ Map<String, SurfaceControl.RefreshRateRange> getRefreshRangeProfiles() {
+ return mRefreshRateZoneProfiles;
+ }
+
+ /**
* @return An array of lower display brightness thresholds. This, in combination with lower
* ambient brightness thresholds help define buckets in which the refresh rate switching is not
* allowed
@@ -1500,6 +1522,7 @@
+ ", mDefaultHighBlockingZoneRefreshRate= " + mDefaultHighBlockingZoneRefreshRate
+ ", mDefaultPeakRefreshRate= " + mDefaultPeakRefreshRate
+ ", mDefaultRefreshRate= " + mDefaultRefreshRate
+ + ", mRefreshRateZoneProfiles= " + mRefreshRateZoneProfiles
+ ", mLowDisplayBrightnessThresholds= "
+ Arrays.toString(mLowDisplayBrightnessThresholds)
+ ", mLowAmbientBrightnessThresholds= "
@@ -1828,6 +1851,7 @@
loadDefaultRefreshRate(refreshRateConfigs);
loadLowerRefreshRateBlockingZones(lowerBlockingZoneConfig);
loadHigherRefreshRateBlockingZones(higherBlockingZoneConfig);
+ loadRefreshRateZoneProfiles(refreshRateConfigs);
}
private void loadPeakDefaultRefreshRate(RefreshRateConfigs refreshRateConfigs) {
@@ -1850,6 +1874,21 @@
}
}
+ /** Loads the refresh rate profiles. */
+ private void loadRefreshRateZoneProfiles(RefreshRateConfigs refreshRateConfigs) {
+ if (refreshRateConfigs == null) {
+ return;
+ }
+ for (RefreshRateZone zone :
+ refreshRateConfigs.getRefreshRateZoneProfiles().getRefreshRateZoneProfile()) {
+ RefreshRateRange range = zone.getRefreshRateRange();
+ mRefreshRateZoneProfiles.put(
+ zone.getId(),
+ new SurfaceControl.RefreshRateRange(
+ range.getMinimum().floatValue(), range.getMaximum().floatValue()));
+ }
+ }
+
/**
* Loads the refresh rate configurations pertaining to the upper blocking zones.
*/
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 5f6660b..29caefb 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -159,7 +159,6 @@
mSupportedModesByDisplay = new SparseArray<>();
mDefaultModeByDisplay = new SparseArray<>();
mAppRequestObserver = new AppRequestObserver();
- mDisplayObserver = new DisplayObserver(context, handler);
mDeviceConfig = injector.getDeviceConfig();
mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
mSettingsObserver = new SettingsObserver(context, handler);
@@ -170,6 +169,7 @@
updateVoteLocked(displayId, priority, vote);
}
};
+ mDisplayObserver = new DisplayObserver(context, handler, ballotBox);
mSensorObserver = new SensorObserver(context, ballotBox, injector);
mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, ballotBox);
mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(),
@@ -1186,26 +1186,29 @@
// rest of low priority voters. It votes [0, max(PEAK, MIN)]
public static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 7;
+ // For concurrent displays we want to limit refresh rate on all displays
+ public static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 8;
+
// LOW_POWER_MODE force the render frame rate to [0, 60HZ] if
// Settings.Global.LOW_POWER_MODE is on.
- public static final int PRIORITY_LOW_POWER_MODE = 8;
+ public static final int PRIORITY_LOW_POWER_MODE = 9;
// PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
// higher priority voters' result is a range, it will fix the rate to a single choice.
// It's used to avoid refresh rate switches in certain conditions which may result in the
// user seeing the display flickering when the switches occur.
- public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 9;
+ public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 10;
// Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
- public static final int PRIORITY_SKIN_TEMPERATURE = 10;
+ public static final int PRIORITY_SKIN_TEMPERATURE = 11;
// The proximity sensor needs the refresh rate to be locked in order to function, so this is
// set to a high priority.
- public static final int PRIORITY_PROXIMITY = 11;
+ public static final int PRIORITY_PROXIMITY = 12;
// The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
// to function, so this needs to be the highest priority of all votes.
- public static final int PRIORITY_UDFPS = 12;
+ public static final int PRIORITY_UDFPS = 13;
// Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
// APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
@@ -1657,10 +1660,12 @@
// calling into us already holding its own lock.
private final Context mContext;
private final Handler mHandler;
+ private final BallotBox mBallotBox;
- DisplayObserver(Context context, Handler handler) {
+ DisplayObserver(Context context, Handler handler, BallotBox ballotBox) {
mContext = context;
mHandler = handler;
+ mBallotBox = ballotBox;
}
public void observe() {
@@ -1689,7 +1694,9 @@
@Override
public void onDisplayAdded(int displayId) {
- updateDisplayModes(displayId);
+ DisplayInfo displayInfo = getDisplayInfo(displayId);
+ updateDisplayModes(displayId, displayInfo);
+ updateLayoutLimitedFrameRate(displayId, displayInfo);
}
@Override
@@ -1698,23 +1705,41 @@
mSupportedModesByDisplay.remove(displayId);
mDefaultModeByDisplay.remove(displayId);
}
+ updateLayoutLimitedFrameRate(displayId, null);
}
@Override
public void onDisplayChanged(int displayId) {
- updateDisplayModes(displayId);
+ DisplayInfo displayInfo = getDisplayInfo(displayId);
+ updateDisplayModes(displayId, displayInfo);
+ updateLayoutLimitedFrameRate(displayId, displayInfo);
}
- private void updateDisplayModes(int displayId) {
+ @Nullable
+ private DisplayInfo getDisplayInfo(int displayId) {
Display d = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
if (d == null) {
// We can occasionally get a display added or changed event for a display that was
// subsequently removed, which means this returns null. Check this case and bail
// out early; if it gets re-attached we'll eventually get another call back for it.
- return;
+ return null;
}
DisplayInfo info = new DisplayInfo();
d.getDisplayInfo(info);
+ return info;
+ }
+
+ private void updateLayoutLimitedFrameRate(int displayId, @Nullable DisplayInfo info) {
+ Vote vote = info != null && info.layoutLimitedRefreshRate != null
+ ? Vote.forPhysicalRefreshRates(info.layoutLimitedRefreshRate.min,
+ info.layoutLimitedRefreshRate.max) : null;
+ mBallotBox.vote(displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, vote);
+ }
+
+ private void updateDisplayModes(int displayId, @Nullable DisplayInfo info) {
+ if (info == null) {
+ return;
+ }
boolean changed = false;
synchronized (mLock) {
if (!Arrays.equals(mSupportedModesByDisplay.get(displayId), info.supportedModes)) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 40eec33..b58d907 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1011,7 +1011,7 @@
}
mBrightnessSettingListener = brightnessValue -> {
Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
};
mBrightnessSetting.registerListener(mBrightnessSettingListener);
@@ -1040,7 +1040,7 @@
final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
R.bool.config_enableIdleScreenBrightnessMode);
- mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources,
+ mInteractiveModeBrightnessMapper = mInjector.getInteractiveModeBrightnessMapper(resources,
mDisplayDeviceConfig, mDisplayWhiteBalanceController);
if (isIdleScreenBrightnessEnabled) {
mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources,
@@ -1065,7 +1065,7 @@
mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
float ambientBrighteningMinThreshold =
mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
- HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
+ HysteresisLevels ambientBrightnessThresholds = mInjector.getHysteresisLevels(
ambientBrighteningThresholds, ambientDarkeningThresholds,
ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
ambientBrighteningMinThreshold);
@@ -1083,7 +1083,7 @@
mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
float screenBrighteningMinThreshold =
mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
- HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
+ HysteresisLevels screenBrightnessThresholds = mInjector.getHysteresisLevels(
screenBrighteningThresholds, screenDarkeningThresholds,
screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
screenBrighteningMinThreshold, true);
@@ -1101,7 +1101,7 @@
mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
float[] ambientDarkeningLevelsIdle =
mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
- HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels(
+ HysteresisLevels ambientBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
@@ -1119,7 +1119,7 @@
mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
float[] screenDarkeningLevelsIdle =
mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
- HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
+ HysteresisLevels screenBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
@@ -1155,8 +1155,8 @@
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.stop();
}
- mAutomaticBrightnessController = new AutomaticBrightnessController(this,
- handler.getLooper(), mSensorManager, mLightSensor,
+ mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
+ this, handler.getLooper(), mSensorManager, mLightSensor,
mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor,
lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
@@ -1257,7 +1257,7 @@
public void onAnimationEnd() {
sendUpdatePowerState();
Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_RAMP_DONE);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
};
@@ -2949,7 +2949,8 @@
msg.what = MSG_STATSD_HBM_BRIGHTNESS;
msg.arg1 = Float.floatToIntBits(brightness);
msg.arg2 = mDisplayStatsId;
- mHandler.sendMessageDelayed(msg, BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()
+ + BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
}
}
}
@@ -3105,7 +3106,7 @@
@Override
public void onScreenOn() {
Message msg = mHandler.obtainMessage(MSG_SCREEN_ON_UNBLOCKED, this);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
}
@@ -3113,7 +3114,7 @@
@Override
public void onScreenOff() {
Message msg = mHandler.obtainMessage(MSG_SCREEN_OFF_UNBLOCKED, this);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
}
@@ -3195,6 +3196,58 @@
FloatProperty<DisplayPowerState> secondProperty) {
return new DualRampAnimator(dps, firstProperty, secondProperty);
}
+
+ AutomaticBrightnessController getAutomaticBrightnessController(
+ AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
+ BrightnessMappingStrategy interactiveModeBrightnessMapper,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
+ HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+ HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+ BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
+ int ambientLightHorizonLong, float userLux, float userBrightness) {
+ return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
+ interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin,
+ brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate,
+ brighteningLightDebounceConfig, darkeningLightDebounceConfig,
+ resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
+ screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
+ screenBrightnessThresholdsIdle, context, hbmController, brightnessThrottler,
+ idleModeBrightnessMapper, ambientLightHorizonShort, ambientLightHorizonLong,
+ userLux, userBrightness);
+ }
+
+ BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+ DisplayDeviceConfig displayDeviceConfig,
+ DisplayWhiteBalanceController displayWhiteBalanceController) {
+ return BrightnessMappingStrategy.create(resources,
+ displayDeviceConfig, displayWhiteBalanceController);
+ }
+
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
+ return new HysteresisLevels(brighteningThresholdsPercentages,
+ darkeningThresholdsPercentages, brighteningThresholdLevels,
+ darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold);
+ }
+
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+ return new HysteresisLevels(brighteningThresholdsPercentages,
+ darkeningThresholdsPercentages, brighteningThresholdLevels,
+ darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold,
+ potentialOldBrightnessRange);
+ }
}
static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 6092ad7..23ef680 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -850,7 +850,7 @@
BrightnessSetting.BrightnessSettingListener brightnessSettingListener = brightnessValue -> {
Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
};
mDisplayBrightnessController
.registerBrightnessSettingChangeListener(brightnessSettingListener);
@@ -880,7 +880,7 @@
final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
R.bool.config_enableIdleScreenBrightnessMode);
- mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources,
+ mInteractiveModeBrightnessMapper = mInjector.getInteractiveModeBrightnessMapper(resources,
mDisplayDeviceConfig, mDisplayWhiteBalanceController);
if (isIdleScreenBrightnessEnabled) {
mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources,
@@ -905,7 +905,7 @@
mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
float ambientBrighteningMinThreshold =
mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
- HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
+ HysteresisLevels ambientBrightnessThresholds = mInjector.getHysteresisLevels(
ambientBrighteningThresholds, ambientDarkeningThresholds,
ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
ambientBrighteningMinThreshold);
@@ -923,7 +923,7 @@
mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
float screenBrighteningMinThreshold =
mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
- HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
+ HysteresisLevels screenBrightnessThresholds = mInjector.getHysteresisLevels(
screenBrighteningThresholds, screenDarkeningThresholds,
screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
screenBrighteningMinThreshold, true);
@@ -941,7 +941,7 @@
mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
float[] ambientDarkeningLevelsIdle =
mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
- HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels(
+ HysteresisLevels ambientBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
@@ -959,7 +959,7 @@
mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
float[] screenDarkeningLevelsIdle =
mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
- HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
+ HysteresisLevels screenBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
@@ -995,8 +995,8 @@
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.stop();
}
- mAutomaticBrightnessController = new AutomaticBrightnessController(this,
- handler.getLooper(), mSensorManager, mLightSensor,
+ mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
+ this, handler.getLooper(), mSensorManager, mLightSensor,
mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor,
lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
@@ -1094,7 +1094,7 @@
public void onAnimationEnd() {
sendUpdatePowerState();
Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_RAMP_DONE);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
};
@@ -2458,7 +2458,8 @@
msg.what = MSG_STATSD_HBM_BRIGHTNESS;
msg.arg1 = Float.floatToIntBits(brightness);
msg.arg2 = mDisplayStatsId;
- mHandler.sendMessageDelayed(msg, BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()
+ + BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
}
}
}
@@ -2589,7 +2590,7 @@
@Override
public void onScreenOn() {
Message msg = mHandler.obtainMessage(MSG_SCREEN_ON_UNBLOCKED, this);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
}
@@ -2597,7 +2598,7 @@
@Override
public void onScreenOff() {
Message msg = mHandler.obtainMessage(MSG_SCREEN_OFF_UNBLOCKED, this);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
}
@@ -2671,6 +2672,58 @@
looper, nudgeUpdatePowerState,
displayId, sensorManager, /* injector= */ null);
}
+
+ AutomaticBrightnessController getAutomaticBrightnessController(
+ AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
+ BrightnessMappingStrategy interactiveModeBrightnessMapper,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
+ HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+ HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
+ BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
+ int ambientLightHorizonLong, float userLux, float userBrightness) {
+ return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
+ interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin,
+ brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate,
+ brighteningLightDebounceConfig, darkeningLightDebounceConfig,
+ resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
+ screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
+ screenBrightnessThresholdsIdle, context, hbmController, brightnessThrottler,
+ idleModeBrightnessMapper, ambientLightHorizonShort, ambientLightHorizonLong,
+ userLux, userBrightness);
+ }
+
+ BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+ DisplayDeviceConfig displayDeviceConfig,
+ DisplayWhiteBalanceController displayWhiteBalanceController) {
+ return BrightnessMappingStrategy.create(resources,
+ displayDeviceConfig, displayWhiteBalanceController);
+ }
+
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
+ return new HysteresisLevels(brighteningThresholdsPercentages,
+ darkeningThresholdsPercentages, brighteningThresholdLevels,
+ darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold);
+ }
+
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+ return new HysteresisLevels(brighteningThresholdsPercentages,
+ darkeningThresholdsPercentages, brighteningThresholdLevels,
+ darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold,
+ potentialOldBrightnessRange);
+ }
}
static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 4bb1f0e..473317c 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -77,6 +77,12 @@
private final int mDisplayId;
private final int mLayerStack;
+ // Indicates which display leads this logical display, in terms of brightness or other
+ // properties.
+ // {@link Layout.NO_LEAD_DISPLAY} means that this display is not lead by any others, and could
+ // be a leader itself.
+ private int mLeadDisplayId = Layout.NO_LEAD_DISPLAY;
+
private int mDisplayGroupId = Display.INVALID_DISPLAY_GROUP;
/**
@@ -150,7 +156,7 @@
// Indicates the display is part of a transition from one device-state ({@link
// DeviceStateManager}) to another. Being a "part" of a transition means that either
- // the {@link mIsEnabled} is changing, or the underlying mPrimiaryDisplayDevice is changing.
+ // the {@link mIsEnabled} is changing, or the underlying mPrimaryDisplayDevice is changing.
private boolean mIsInTransition;
// Indicates the position of the display, POSITION_UNKNOWN could mean it hasn't been specified,
@@ -826,6 +832,27 @@
brightnessThrottlingDataId;
}
+ /**
+ * Sets the display of which this display is a follower, regarding brightness or other
+ * properties. If set to {@link Layout#NO_LEAD_DISPLAY}, this display does not follow any
+ * others, and has the potential to be a lead display to others.
+ *
+ * A display cannot be a leader or follower of itself, and there cannot be cycles.
+ * A display cannot be both a leader and a follower, ie, there must not be any chains.
+ *
+ * @param displayId logical display id
+ */
+ public void setLeadDisplayLocked(int displayId) {
+ if (mDisplayId != mLeadDisplayId && mDisplayId != displayId) {
+ mLeadDisplayId = displayId;
+ }
+ }
+
+ public int getLeadDisplayLocked() {
+ return mLeadDisplayId;
+
+ }
+
public void dumpLocked(PrintWriter pw) {
pw.println("mDisplayId=" + mDisplayId);
pw.println("mIsEnabled=" + mIsEnabled);
@@ -845,6 +872,7 @@
pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
pw.println("mBrightnessThrottlingDataId=" + mBrightnessThrottlingDataId);
+ pw.println("mLeadDisplayId=" + mLeadDisplayId);
}
@Override
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index a67644b..56c9056 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -18,6 +18,8 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.server.display.layout.Layout.NO_LEAD_DISPLAY;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -639,7 +641,7 @@
&& !nextDeviceInfo.address.equals(deviceInfo.address)) {
layout.createDisplayLocked(nextDeviceInfo.address,
/* isDefault= */ true, /* isEnabled= */ true, mIdProducer,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null, DEFAULT_DISPLAY);
applyLayoutLocked();
return;
}
@@ -991,15 +993,22 @@
}
newDisplay.setPositionLocked(displayLayout.getPosition());
+ newDisplay.setLeadDisplayLocked(displayLayout.getLeadDisplayId());
+ setLayoutLimitedRefreshRate(newDisplay, device, displayLayout);
setEnabledLocked(newDisplay, displayLayout.isEnabled());
newDisplay.setBrightnessThrottlingDataIdLocked(
displayLayout.getBrightnessThrottlingMapId() == null
? DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID
: displayLayout.getBrightnessThrottlingMapId());
}
-
}
+ private void setLayoutLimitedRefreshRate(@NonNull LogicalDisplay logicalDisplay,
+ @NonNull DisplayDevice device, @NonNull Layout.Display display) {
+ DisplayDeviceConfig config = device.getDisplayDeviceConfig();
+ DisplayInfo info = logicalDisplay.getDisplayInfoLocked();
+ info.layoutLimitedRefreshRate = config.getRefreshRange(display.getRefreshRateZoneId());
+ }
/**
* Creates a new logical display for the specified device and display Id and adds it to the list
@@ -1070,7 +1079,7 @@
}
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true,
- mIdProducer, /* brightnessThrottlingMapId= */ null);
+ mIdProducer, /* brightnessThrottlingMapId= */ null, NO_LEAD_DISPLAY);
}
private int assignLayerStackLocked(int displayId) {
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 01aa97a..59d95a6 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -39,6 +39,10 @@
private static final String TAG = "Layout";
private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
+ // Lead display Id is set to this if this is not a follower display, and therefore
+ // has no lead.
+ public static final int NO_LEAD_DISPLAY = -1;
+
private final List<Display> mDisplays = new ArrayList<>(2);
/**
@@ -75,13 +79,16 @@
* @param address Address of the device.
* @param isDefault Indicates if the device is meant to be the default display.
* @param isEnabled Indicates if this display is usable and can be switched on
- * @return The new layout.
+ * @param idProducer Produces the logical display id.
+ * @param brightnessThrottlingMapId Name of which throttling policy should be used.
+ * @param leadDisplayId Display that this one follows (-1 if none).
+ * @return The new Display.
*/
public Display createDisplayLocked(
@NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
- DisplayIdProducer idProducer, String brightnessThrottlingMapId) {
+ DisplayIdProducer idProducer, String brightnessThrottlingMapId, int leadDisplayId) {
return createDisplayLocked(address, isDefault, isEnabled, idProducer,
- brightnessThrottlingMapId, POSITION_UNKNOWN);
+ brightnessThrottlingMapId, POSITION_UNKNOWN, leadDisplayId);
}
/**
@@ -90,12 +97,16 @@
* @param address Address of the device.
* @param isDefault Indicates if the device is meant to be the default display.
* @param isEnabled Indicates if this display is usable and can be switched on
+ * @param idProducer Produces the logical display id.
+ * @param brightnessThrottlingMapId Name of which throttling policy should be used.
* @param position Indicates the position this display is facing in this layout.
- * @return The new layout.
+ * @param leadDisplayId Display that this one follows (-1 if none).
+ * @return The new Display.
*/
public Display createDisplayLocked(
@NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
- DisplayIdProducer idProducer, String brightnessThrottlingMapId, int position) {
+ DisplayIdProducer idProducer, String brightnessThrottlingMapId, int position,
+ int leadDisplayId) {
if (contains(address)) {
Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
return null;
@@ -113,7 +124,7 @@
// same logical display ID.
final int logicalDisplayId = idProducer.getId(isDefault);
final Display display = new Display(address, logicalDisplayId, isEnabled,
- brightnessThrottlingMapId, position);
+ brightnessThrottlingMapId, position, leadDisplayId);
mDisplays.add(display);
return display;
@@ -221,13 +232,27 @@
@Nullable
private final String mBrightnessThrottlingMapId;
+ // The ID of the lead display that this display will follow in a layout. -1 means no lead.
+ private int mLeadDisplayId;
+
+ // Refresh rate zone id for specific layout
+ @Nullable
+ private String mRefreshRateZoneId;
+
Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
- String brightnessThrottlingMapId, int position) {
+ String brightnessThrottlingMapId, int position, int leadDisplayId) {
mAddress = address;
mLogicalDisplayId = logicalDisplayId;
mIsEnabled = isEnabled;
mPosition = position;
mBrightnessThrottlingMapId = brightnessThrottlingMapId;
+
+ if (leadDisplayId == mLogicalDisplayId) {
+ mLeadDisplayId = NO_LEAD_DISPLAY;
+ } else {
+ mLeadDisplayId = leadDisplayId;
+ }
+
}
@Override
@@ -238,6 +263,8 @@
+ ", addr: " + mAddress
+ ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition)
+ ", brightnessThrottlingMapId: " + mBrightnessThrottlingMapId
+ + ", mRefreshRateZoneId: " + mRefreshRateZoneId
+ + ", mLeadDisplayId: " + mLeadDisplayId
+ "}";
}
@@ -254,7 +281,9 @@
&& otherDisplay.mLogicalDisplayId == this.mLogicalDisplayId
&& this.mAddress.equals(otherDisplay.mAddress)
&& Objects.equals(mBrightnessThrottlingMapId,
- otherDisplay.mBrightnessThrottlingMapId);
+ otherDisplay.mBrightnessThrottlingMapId)
+ && Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId)
+ && this.mLeadDisplayId == otherDisplay.mLeadDisplayId;
}
@Override
@@ -265,6 +294,8 @@
result = 31 * result + mLogicalDisplayId;
result = 31 * result + mAddress.hashCode();
result = 31 * result + mBrightnessThrottlingMapId.hashCode();
+ result = 31 * result + Objects.hashCode(mRefreshRateZoneId);
+ result = 31 * result + mLeadDisplayId;
return result;
}
@@ -280,6 +311,20 @@
return mIsEnabled;
}
+
+ public void setRefreshRateZoneId(@Nullable String refreshRateZoneId) {
+ mRefreshRateZoneId = refreshRateZoneId;
+ }
+
+ @Nullable
+ public String getRefreshRateZoneId() {
+ return mRefreshRateZoneId;
+ }
+
+ /**
+ * Sets the position that this display is facing.
+ * @param position the display is facing.
+ */
public void setPosition(int position) {
mPosition = position;
}
@@ -291,8 +336,31 @@
return mBrightnessThrottlingMapId;
}
+ /**
+ *
+ * @return the position that this display is facing.
+ */
public int getPosition() {
return mPosition;
}
+
+ /**
+ * Set the display that this display should follow certain properties of, for example,
+ * brightness
+ * @param displayId of the lead display.
+ */
+ public void setLeadDisplay(int displayId) {
+ if (displayId != mLogicalDisplayId) {
+ mLeadDisplayId = displayId;
+ }
+ }
+
+ /**
+ *
+ * @return logical displayId of the display that this one follows.
+ */
+ public int getLeadDisplayId() {
+ return mLeadDisplayId;
+ }
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index e2caeec..84e63ec 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -32,7 +32,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.database.ContentObserver;
import android.graphics.PointF;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManager.Sensors;
@@ -78,8 +77,6 @@
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.provider.DeviceConfig;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
@@ -147,8 +144,6 @@
private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
private static final String PORT_ASSOCIATIONS_PATH = "etc/input-port-associations.xml";
- // Feature flag name for the deep press feature
- private static final String DEEP_PRESS_ENABLED = "deep_press_enabled";
// Feature flag name for the strategy to be used in VelocityTracker
private static final String VELOCITYTRACKER_STRATEGY_PROPERTY = "velocitytracker_strategy";
@@ -307,6 +302,9 @@
@GuardedBy("mInputMonitors")
final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
+ // Watches for settings changes and updates the native side appropriately.
+ private final InputSettingsObserver mSettingsObserver;
+
// Manages Keyboard layouts for Physical keyboards
private final KeyboardLayoutManager mKeyboardLayoutManager;
@@ -428,6 +426,7 @@
mContext = injector.getContext();
mHandler = new InputManagerHandler(injector.getLooper());
mNative = injector.getNativeService(this);
+ mSettingsObserver = new InputSettingsObserver(mContext, mHandler, mNative);
mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore,
injector.getLooper());
mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
@@ -493,39 +492,7 @@
// Add ourselves to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
- registerMousePointerSpeedSettingObserver();
- registerTouchpadPointerSpeedSettingObserver();
- registerTouchpadNaturalScrollingEnabledObserver();
- registerTouchpadTapToClickEnabledObserver();
- registerTouchpadRightClickZoneEnabledObserver();
- registerShowTouchesSettingObserver();
- registerAccessibilityLargePointerSettingObserver();
- registerLongPressTimeoutObserver();
- registerMaximumObscuringOpacityForTouchSettingObserver();
-
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- updateMousePointerSpeedFromSettings();
- updateTouchpadPointerSpeedFromSettings();
- updateTouchpadNaturalScrollingEnabledFromSettings();
- updateTouchpadTapToClickEnabledFromSettings();
- updateTouchpadRightClickZoneEnabledFromSettings();
- updateShowTouchesFromSettings();
- updateAccessibilityLargePointerFromSettings();
- updateDeepPressStatusFromSettings("user switched");
- }
- }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
-
- updateMousePointerSpeedFromSettings();
- updateTouchpadPointerSpeedFromSettings();
- updateTouchpadNaturalScrollingEnabledFromSettings();
- updateTouchpadTapToClickEnabledFromSettings();
- updateTouchpadRightClickZoneEnabledFromSettings();
- updateShowTouchesFromSettings();
- updateAccessibilityLargePointerFromSettings();
- updateDeepPressStatusFromSettings("just booted");
- updateMaximumObscuringOpacityForTouchFromSettings();
+ mSettingsObserver.registerAndUpdate();
}
// TODO(BT) Pass in parameter for bluetooth system
@@ -1349,11 +1316,6 @@
setPointerSpeedUnchecked(speed);
}
- private void updateMousePointerSpeedFromSettings() {
- int speed = getMousePointerSpeedSetting();
- setPointerSpeedUnchecked(speed);
- }
-
private void setPointerSpeedUnchecked(int speed) {
speed = Math.min(Math.max(speed, InputManager.MIN_POINTER_SPEED),
InputManager.MAX_POINTER_SPEED);
@@ -1370,194 +1332,6 @@
properties -> properties.pointerIconVisible = visible);
}
- private void registerMousePointerSpeedSettingObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.POINTER_SPEED), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateMousePointerSpeedFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private int getMousePointerSpeedSetting() {
- int speed = InputManager.DEFAULT_POINTER_SPEED;
- try {
- speed = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.POINTER_SPEED, UserHandle.USER_CURRENT);
- } catch (SettingNotFoundException ignored) {
- }
- return speed;
- }
-
- private void registerTouchpadPointerSpeedSettingObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.TOUCHPAD_POINTER_SPEED), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateTouchpadPointerSpeedFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private void updateTouchpadPointerSpeedFromSettings() {
- int speed = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.TOUCHPAD_POINTER_SPEED, InputManager.DEFAULT_POINTER_SPEED,
- UserHandle.USER_CURRENT);
- speed = Math.min(Math.max(speed, InputManager.MIN_POINTER_SPEED),
- InputManager.MAX_POINTER_SPEED);
- mNative.setTouchpadPointerSpeed(speed);
- }
-
- private void registerTouchpadNaturalScrollingEnabledObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateTouchpadNaturalScrollingEnabledFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private void updateTouchpadNaturalScrollingEnabledFromSettings() {
- int setting = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.TOUCHPAD_NATURAL_SCROLLING, 0, UserHandle.USER_CURRENT);
- mNative.setTouchpadNaturalScrollingEnabled(setting != 0);
- }
-
- private void registerTouchpadTapToClickEnabledObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.TOUCHPAD_TAP_TO_CLICK), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateTouchpadTapToClickEnabledFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private void updateTouchpadTapToClickEnabledFromSettings() {
- int setting = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.TOUCHPAD_TAP_TO_CLICK, 0, UserHandle.USER_CURRENT);
- mNative.setTouchpadTapToClickEnabled(setting != 0);
- }
-
- private void registerTouchpadRightClickZoneEnabledObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateTouchpadRightClickZoneEnabledFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private void updateTouchpadRightClickZoneEnabledFromSettings() {
- int setting = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE, 0, UserHandle.USER_CURRENT);
- mNative.setTouchpadRightClickZoneEnabled(setting != 0);
- }
-
- private void updateShowTouchesFromSettings() {
- int setting = getShowTouchesSetting(0);
- mNative.setShowTouches(setting != 0);
- }
-
- private void registerShowTouchesSettingObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.SHOW_TOUCHES), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateShowTouchesFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private void updateAccessibilityLargePointerFromSettings() {
- final int accessibilityConfig = Settings.Secure.getIntForUser(
- mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
- 0, UserHandle.USER_CURRENT);
- PointerIcon.setUseLargeIcons(accessibilityConfig == 1);
- mNative.reloadPointerIcons();
- }
-
- private void registerAccessibilityLargePointerSettingObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateAccessibilityLargePointerFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private void updateDeepPressStatusFromSettings(String reason) {
- // Not using ViewConfiguration.getLongPressTimeout here because it may return a stale value
- final int timeout = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.LONG_PRESS_TIMEOUT, ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT,
- UserHandle.USER_CURRENT);
- final boolean featureEnabledFlag =
- DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
- DEEP_PRESS_ENABLED, true /* default */);
- final boolean enabled =
- featureEnabledFlag && timeout <= ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
- Log.i(TAG,
- (enabled ? "Enabling" : "Disabling") + " motion classifier because " + reason
- + ": feature " + (featureEnabledFlag ? "enabled" : "disabled")
- + ", long press timeout = " + timeout);
- mNative.setMotionClassifierEnabled(enabled);
- }
-
- private void registerLongPressTimeoutObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LONG_PRESS_TIMEOUT), true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateDeepPressStatusFromSettings("timeout changed");
- }
- }, UserHandle.USER_ALL);
- }
-
- private void registerMaximumObscuringOpacityForTouchSettingObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH),
- /* notifyForDescendants */ true,
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- updateMaximumObscuringOpacityForTouchFromSettings();
- }
- }, UserHandle.USER_ALL);
- }
-
- private void updateMaximumObscuringOpacityForTouchFromSettings() {
- InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
- final float opacity = im.getMaximumObscuringOpacityForTouch();
- if (opacity < 0 || opacity > 1) {
- Log.e(TAG, "Invalid maximum obscuring opacity " + opacity
- + ", it should be >= 0 and <= 1, rejecting update.");
- return;
- }
- mNative.setMaximumObscuringOpacityForTouch(opacity);
- }
-
- private int getShowTouchesSetting(int defaultValue) {
- int result = defaultValue;
- try {
- result = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.SHOW_TOUCHES, UserHandle.USER_CURRENT);
- } catch (SettingNotFoundException snfe) {
- }
- return result;
- }
-
/**
* Update the display on which the mouse pointer is shown.
*
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
new file mode 100644
index 0000000..8ee3a72
--- /dev/null
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -0,0 +1,181 @@
+/*
+ * 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.input;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.hardware.input.InputManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.PointerIcon;
+import android.view.ViewConfiguration;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+/** Observes settings changes and propagates them to the native side. */
+class InputSettingsObserver extends ContentObserver {
+ static final String TAG = "InputManager";
+
+ /** Feature flag name for the deep press feature */
+ private static final String DEEP_PRESS_ENABLED = "deep_press_enabled";
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final NativeInputManagerService mNative;
+ private final Map<Uri, Consumer<String /* reason*/>> mObservers;
+
+ InputSettingsObserver(Context context, Handler handler, NativeInputManagerService nativeIms) {
+ super(handler);
+ mContext = context;
+ mHandler = handler;
+ mNative = nativeIms;
+ mObservers = Map.ofEntries(
+ Map.entry(Settings.System.getUriFor(Settings.System.POINTER_SPEED),
+ (reason) -> updateMousePointerSpeed()),
+ Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_POINTER_SPEED),
+ (reason) -> updateTouchpadPointerSpeed()),
+ Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING),
+ (reason) -> updateTouchpadNaturalScrollingEnabled()),
+ Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_TAP_TO_CLICK),
+ (reason) -> updateTouchpadTapToClickEnabled()),
+ Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE),
+ (reason) -> updateTouchpadRightClickZoneEnabled()),
+ Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
+ (reason) -> updateShowTouches()),
+ Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON),
+ (reason) -> updateAccessibilityLargePointer()),
+ Map.entry(Settings.Secure.getUriFor(Settings.Secure.LONG_PRESS_TIMEOUT),
+ (reason) -> updateDeepPressStatus(reason)),
+ Map.entry(
+ Settings.Global.getUriFor(Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH),
+ (reason) -> updateMaximumObscuringOpacityForTouch()));
+ }
+
+ /**
+ * Registers observers for input-related settings and updates the input subsystem with their
+ * current values.
+ */
+ public void registerAndUpdate() {
+ for (Uri uri : mObservers.keySet()) {
+ mContext.getContentResolver().registerContentObserver(
+ uri, true /* notifyForDescendants */, this, UserHandle.USER_ALL);
+ }
+
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ for (Consumer<String> observer : mObservers.values()) {
+ observer.accept("user switched");
+ }
+ }
+ }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
+
+ for (Consumer<String> observer : mObservers.values()) {
+ observer.accept("just booted");
+ }
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ mObservers.get(uri).accept("setting changed");
+ }
+
+ private boolean getBoolean(String settingName) {
+ final int setting = Settings.System.getIntForUser(mContext.getContentResolver(),
+ settingName, 0, UserHandle.USER_CURRENT);
+ return setting != 0;
+ }
+
+ private int getPointerSpeedValue(String settingName) {
+ int speed = Settings.System.getIntForUser(mContext.getContentResolver(),
+ settingName, InputManager.DEFAULT_POINTER_SPEED, UserHandle.USER_CURRENT);
+ return Math.min(Math.max(speed, InputManager.MIN_POINTER_SPEED),
+ InputManager.MAX_POINTER_SPEED);
+ }
+
+ private void updateMousePointerSpeed() {
+ mNative.setPointerSpeed(getPointerSpeedValue(Settings.System.POINTER_SPEED));
+ }
+
+ private void updateTouchpadPointerSpeed() {
+ mNative.setTouchpadPointerSpeed(
+ getPointerSpeedValue(Settings.System.TOUCHPAD_POINTER_SPEED));
+ }
+
+ private void updateTouchpadNaturalScrollingEnabled() {
+ mNative.setTouchpadNaturalScrollingEnabled(
+ getBoolean(Settings.System.TOUCHPAD_NATURAL_SCROLLING));
+ }
+
+ private void updateTouchpadTapToClickEnabled() {
+ mNative.setTouchpadTapToClickEnabled(getBoolean(Settings.System.TOUCHPAD_TAP_TO_CLICK));
+ }
+
+ private void updateTouchpadRightClickZoneEnabled() {
+ mNative.setTouchpadRightClickZoneEnabled(
+ getBoolean(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE));
+ }
+
+ private void updateShowTouches() {
+ mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES));
+ }
+
+ private void updateAccessibilityLargePointer() {
+ final int accessibilityConfig = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
+ 0, UserHandle.USER_CURRENT);
+ PointerIcon.setUseLargeIcons(accessibilityConfig == 1);
+ mNative.reloadPointerIcons();
+ }
+
+ private void updateDeepPressStatus(String reason) {
+ // Not using ViewConfiguration.getLongPressTimeout here because it may return a stale value
+ final int timeout = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.LONG_PRESS_TIMEOUT, ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT,
+ UserHandle.USER_CURRENT);
+ final boolean featureEnabledFlag =
+ DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
+ DEEP_PRESS_ENABLED, true /* default */);
+ final boolean enabled =
+ featureEnabledFlag && timeout <= ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
+ Log.i(TAG,
+ (enabled ? "Enabling" : "Disabling") + " motion classifier because " + reason
+ + ": feature " + (featureEnabledFlag ? "enabled" : "disabled")
+ + ", long press timeout = " + timeout);
+ mNative.setMotionClassifierEnabled(enabled);
+ }
+
+ private void updateMaximumObscuringOpacityForTouch() {
+ InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
+ final float opacity = im.getMaximumObscuringOpacityForTouch();
+ if (opacity < 0 || opacity > 1) {
+ Log.e(TAG, "Invalid maximum obscuring opacity " + opacity
+ + ", it should be >= 0 and <= 1, rejecting update.");
+ return;
+ }
+ mNative.setMaximumObscuringOpacityForTouch(opacity);
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index cd4a8f3..c7f4a49 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -29,6 +29,7 @@
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.UserHandle.USER_ALL;
+import static android.os.UserHandle.USER_SYSTEM;
import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
@@ -70,6 +71,7 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.sqlite.SQLiteDatabase;
import android.hardware.authsecret.IAuthSecret;
@@ -219,6 +221,8 @@
private static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_";
private static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
+ private static final int HEADLESS_VENDOR_AUTH_SECRET_LENGTH = 32;
+
// Order of holding lock: mSeparateChallengeLock -> mSpManager -> this
// Do not call into ActivityManager while holding mSpManager lock.
private final Object mSeparateChallengeLock = new Object();
@@ -267,6 +271,13 @@
@VisibleForTesting
protected boolean mHasSecureLockScreen;
+ @VisibleForTesting
+ protected final Object mHeadlessAuthSecretLock = new Object();
+
+ @VisibleForTesting
+ @GuardedBy("mHeadlessAuthSecretLock")
+ protected byte[] mAuthSecret;
+
protected IGateKeeperService mGateKeeperService;
protected IAuthSecret mAuthSecretService;
@@ -563,6 +574,15 @@
java.security.KeyStore ks) {
return new ManagedProfilePasswordCache(ks, getUserManager());
}
+
+ public boolean isHeadlessSystemUserMode() {
+ return UserManager.isHeadlessSystemUserMode();
+ }
+
+ public boolean isMainUserPermanentAdmin() {
+ return Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_isMainUserPermanentAdmin);
+ }
}
public LockSettingsService(Context context) {
@@ -1697,7 +1717,7 @@
throw new IllegalStateException("password change failed");
}
- onSyntheticPasswordKnown(userId, sp);
+ onSyntheticPasswordUnlocked(userId, sp);
setLockCredentialWithSpLocked(credential, sp, userId);
sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
return true;
@@ -2009,7 +2029,7 @@
Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
return;
}
- onSyntheticPasswordKnown(userId, result.syntheticPassword);
+ onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
unlockUserKey(userId, result.syntheticPassword);
}
}
@@ -2602,43 +2622,112 @@
}
}
- private void onSyntheticPasswordKnown(@UserIdInt int userId, SyntheticPassword sp) {
+ private void onSyntheticPasswordCreated(@UserIdInt int userId, SyntheticPassword sp) {
+ onSyntheticPasswordKnown(userId, sp, true);
+ }
+
+ private void onSyntheticPasswordUnlocked(@UserIdInt int userId, SyntheticPassword sp) {
+ onSyntheticPasswordKnown(userId, sp, false);
+ }
+
+ private void onSyntheticPasswordKnown(
+ @UserIdInt int userId, SyntheticPassword sp, boolean justCreated) {
if (mInjector.isGsiRunning()) {
Slog.w(TAG, "Running in GSI; skipping calls to AuthSecret and RebootEscrow");
return;
}
- mRebootEscrowManager.callToRebootEscrowIfNeeded(userId, sp.getVersion(),
- sp.getSyntheticPassword());
-
- callToAuthSecretIfNeeded(userId, sp);
+ mRebootEscrowManager.callToRebootEscrowIfNeeded(
+ userId, sp.getVersion(), sp.getSyntheticPassword());
+ callToAuthSecretIfNeeded(userId, sp, justCreated);
}
- private void callToAuthSecretIfNeeded(@UserIdInt int userId, SyntheticPassword sp) {
- // If the given user is the primary user, pass the auth secret to the HAL. Only the system
- // user can be primary. Check for the system user ID before calling getUserInfo(), as other
- // users may still be under construction.
+ /**
+ * Handles generation, storage, and sending of the vendor auth secret. Here we try to retrieve
+ * the auth secret to send it to the auth secret HAL, generate a fresh secret if need be, store
+ * it encrypted on disk so that the given user can unlock it in future, and stash it in memory
+ * so that when future users are created they can also unlock it.
+ *
+ * <p>Called whenever the SP of a user is available, except in GSI.
+ */
+ private void callToAuthSecretIfNeeded(
+ @UserIdInt int userId, SyntheticPassword sp, boolean justCreated) {
if (mAuthSecretService == null) {
+ // If there's no IAuthSecret service, we don't need to maintain a auth secret
return;
}
- if (userId == UserHandle.USER_SYSTEM &&
- mUserManager.getUserInfo(userId).isPrimary()) {
- final byte[] secret = sp.deriveVendorAuthSecret();
- try {
- mAuthSecretService.setPrimaryUserCredential(secret);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL", e);
+ // User may be partially created, so use the internal user manager interface
+ final UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
+ final UserInfo userInfo = userManagerInternal.getUserInfo(userId);
+ if (userInfo == null) {
+ // User may be partially deleted, skip this.
+ return;
+ }
+ final byte[] authSecret;
+ if (!mInjector.isHeadlessSystemUserMode()) {
+ // On non-headless systems, the auth secret is derived from user 0's
+ // SP, and only user 0 passes it to the HAL.
+ if (userId != USER_SYSTEM) {
+ return;
}
+ authSecret = sp.deriveVendorAuthSecret();
+ } else if (!mInjector.isMainUserPermanentAdmin() || !userInfo.isFull()) {
+ // Only full users can receive or pass on the auth secret.
+ // If there is no main permanent admin user, we don't try to create or send
+ // an auth secret, since there may sometimes be no full users.
+ return;
+ } else if (justCreated) {
+ if (userInfo.isMain()) {
+ // The first user is just being created, so we create a new auth secret
+ // at the same time.
+ Slog.i(TAG, "Generating new vendor auth secret and storing for user: " + userId);
+ authSecret = SecureRandomUtils.randomBytes(HEADLESS_VENDOR_AUTH_SECRET_LENGTH);
+ // Store it in memory, for when new users are created.
+ synchronized (mHeadlessAuthSecretLock) {
+ mAuthSecret = authSecret;
+ }
+ } else {
+ // A new user is being created. Another user should already have logged in at
+ // this point, and therefore the auth secret should be stored in memory.
+ synchronized (mHeadlessAuthSecretLock) {
+ authSecret = mAuthSecret;
+ }
+ if (authSecret == null) {
+ Slog.e(TAG, "Creating non-main user " + userId
+ + " but vendor auth secret is not in memory");
+ return;
+ }
+ }
+ // Store the auth secret encrypted using the user's SP (which was just created).
+ mSpManager.writeVendorAuthSecret(authSecret, sp, userId);
+ } else {
+ // The user already exists, so the auth secret should be stored encrypted
+ // with that user's SP.
+ authSecret = mSpManager.readVendorAuthSecret(sp, userId);
+ if (authSecret == null) {
+ Slog.e(TAG, "Unable to read vendor auth secret for user: " + userId);
+ return;
+ }
+ // Store it in memory, for when new users are created.
+ synchronized (mHeadlessAuthSecretLock) {
+ mAuthSecret = authSecret;
+ }
+ }
+ Slog.i(TAG, "Sending vendor auth secret to IAuthSecret HAL as user: " + userId);
+ try {
+ mAuthSecretService.setPrimaryUserCredential(authSecret);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to send vendor auth secret to IAuthSecret HAL", e);
}
}
/**
* Creates the synthetic password (SP) for the given user, protects it with an empty LSKF, and
* protects the user's CE key with a key derived from the SP.
- * <p>
- * This is called just once in the lifetime of the user: at user creation time (possibly delayed
- * until the time when Weaver is guaranteed to be available), or when upgrading from Android 13
- * or earlier where users with no LSKF didn't necessarily have an SP.
+ *
+ * <p>This is called just once in the lifetime of the user: at user creation time (possibly
+ * delayed until the time when Weaver is guaranteed to be available), or when upgrading from
+ * Android 13 or earlier where users with no LSKF didn't necessarily have an SP.
*/
@VisibleForTesting
SyntheticPassword initializeSyntheticPassword(int userId) {
@@ -2653,7 +2742,7 @@
LockscreenCredential.createNone(), sp, userId);
setCurrentLskfBasedProtectorId(protectorId, userId);
setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
- onSyntheticPasswordKnown(userId, sp);
+ onSyntheticPasswordCreated(userId, sp);
return sp;
}
}
@@ -2720,7 +2809,7 @@
}
mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
- onSyntheticPasswordKnown(userId, sp);
+ onSyntheticPasswordUnlocked(userId, sp);
}
private void setDeviceUnlockedForUser(int userId) {
@@ -3008,7 +3097,7 @@
+ "verification.");
return false;
}
- onSyntheticPasswordKnown(userId, result.syntheticPassword);
+ onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
setLockCredentialWithSpLocked(credential, result.syntheticPassword, userId);
return true;
}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index ea000a0..c21c945 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.admin.PasswordMetrics;
import android.content.Context;
import android.content.pm.UserInfo;
@@ -93,6 +94,9 @@
* while the LSKF is nonempty.
* SP_E0_NAME, SP_P1_NAME: Information needed to create and use escrow token-based protectors.
* Deleted when escrow token support is disabled for the user.
+ * VENDOR_AUTH_SECRET_NAME: A copy of the secret passed using the IAuthSecret interface,
+ * encrypted using a secret derived from the SP using
+ * PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY.
*
* For each protector, stored under the corresponding protector ID:
* SP_BLOB_NAME: The encrypted SP secret (the SP itself or the P0 value). Always exists.
@@ -120,6 +124,7 @@
private static final String PASSWORD_DATA_NAME = "pwd";
private static final String WEAVER_SLOT_NAME = "weaver";
private static final String PASSWORD_METRICS_NAME = "metrics";
+ private static final String VENDOR_AUTH_SECRET_NAME = "vendor_auth_secret";
// used for files associated with the SP itself, not with a particular protector
public static final long NULL_PROTECTOR_ID = 0L;
@@ -158,6 +163,8 @@
private static final byte[] PERSONALIZATION_SP_GK_AUTH = "sp-gk-authentication".getBytes();
private static final byte[] PERSONALIZATION_FBE_KEY = "fbe-key".getBytes();
private static final byte[] PERSONALIZATION_AUTHSECRET_KEY = "authsecret-hal".getBytes();
+ private static final byte[] PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY =
+ "vendor-authsecret-encryption-key".getBytes();
private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes();
private static final byte[] PERSONALIZATION_PASSWORD_HASH = "pw-hash".getBytes();
private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes();
@@ -249,6 +256,10 @@
return deriveSubkey(PERSONALIZATION_PASSWORD_METRICS);
}
+ public byte[] deriveVendorAuthSecretEncryptionKey() {
+ return deriveSubkey(PERSONALIZATION_AUTHSECRET_ENCRYPTION_KEY);
+ }
+
/**
* Assigns escrow data to this synthetic password. This is a prerequisite to call
* {@link SyntheticPassword#recreateFromEscrow}.
@@ -1737,4 +1748,25 @@
mListeners.finishBroadcast();
}
}
+
+ public void writeVendorAuthSecret(
+ @NonNull final byte[] vendorAuthSecret,
+ @NonNull final SyntheticPassword sp,
+ @UserIdInt final int userId) {
+ final byte[] encrypted =
+ SyntheticPasswordCrypto.encrypt(
+ sp.deriveVendorAuthSecretEncryptionKey(), new byte[0], vendorAuthSecret);
+ saveState(VENDOR_AUTH_SECRET_NAME, encrypted, NULL_PROTECTOR_ID, userId);
+ syncState(userId);
+ }
+
+ public @Nullable byte[] readVendorAuthSecret(
+ @NonNull final SyntheticPassword sp, @UserIdInt final int userId) {
+ final byte[] encrypted = loadState(VENDOR_AUTH_SECRET_NAME, NULL_PROTECTOR_ID, userId);
+ if (encrypted == null) {
+ return null;
+ }
+ return SyntheticPasswordCrypto.decrypt(
+ sp.deriveVendorAuthSecretEncryptionKey(), new byte[0], encrypted);
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 14ae2a7..ce921d6 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -24,6 +24,7 @@
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.FLAG_INSISTENT;
import static android.app.Notification.FLAG_NO_CLEAR;
+import static android.app.Notification.FLAG_NO_DISMISS;
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
@@ -576,6 +577,9 @@
private float mInCallNotificationVolume;
private Binder mCallNotificationToken = null;
+ private static final boolean ONGOING_DISMISSAL = SystemProperties.getBoolean(
+ "persist.sysui.notification.ongoing_dismissal", true);
+
// used as a mutex for access to all active notifications & listeners
final Object mNotificationLock = new Object();
@GuardedBy("mNotificationLock")
@@ -1201,10 +1205,13 @@
id = r.getSbn().getId();
}
}
- int mustNotHaveFlags = FLAG_ONGOING_EVENT;
- cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
- mustNotHaveFlags,
- true, userId, REASON_CANCEL, nv.rank, nv.count,null);
+
+ int mustNotHaveFlags = ONGOING_DISMISSAL ? FLAG_NO_DISMISS : FLAG_ONGOING_EVENT;
+ cancelNotification(callingUid, callingPid, pkg, tag, id,
+ /* mustHaveFlags= */ 0,
+ /* mustNotHaveFlags= */ mustNotHaveFlags,
+ /* sendDelete= */ true,
+ userId, REASON_CANCEL, nv.rank, nv.count, /* listener= */ null);
nv.recycle();
}
@@ -6689,6 +6696,16 @@
(userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
Notification.addFieldsFromContext(ai, notification);
+ // Only notifications that can be non-dismissible can have the flag FLAG_NO_DISMISS
+ if (ONGOING_DISMISSAL) {
+ if (((notification.flags & FLAG_ONGOING_EVENT) > 0)
+ && canBeNonDismissible(ai, notification)) {
+ notification.flags |= FLAG_NO_DISMISS;
+ } else {
+ notification.flags &= ~FLAG_NO_DISMISS;
+ }
+ }
+
int canColorize = mPackageManagerClient.checkPermission(
android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg);
if (canColorize == PERMISSION_GRANTED) {
@@ -6775,6 +6792,23 @@
checkRemoteViews(pkg, tag, id, notification);
}
+ /**
+ * Whether a notification can be non-dismissible.
+ * A notification should be dismissible, unless it's exempted for some reason.
+ */
+ private boolean canBeNonDismissible(ApplicationInfo ai, Notification notification) {
+ // Check if the app is on the system partition, or an update to an app on the system
+ // partition.
+ boolean isSystemAppExempt = (ai.flags
+ & (ApplicationInfo.FLAG_UPDATED_SYSTEM_APP | ApplicationInfo.FLAG_SYSTEM)) > 0;
+ return isSystemAppExempt || notification.isMediaNotification() || isEnterpriseExempted();
+ }
+
+ // TODO: b/266237746 Enterprise app exemptions
+ private boolean isEnterpriseExempted() {
+ return false;
+ }
+
private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
if (removeRemoteView(pkg, tag, id, notification.contentView)) {
notification.contentView = null;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index f77d38f..55dcaf6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3996,7 +3996,14 @@
return;
}
}
- r.run();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // This will call into StagingManager which might trigger external callbacks
+ r.run();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 9c91879..7e7205d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -120,7 +120,7 @@
import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationPersistence;
-import com.android.server.security.FileIntegrityLocal;
+import com.android.server.security.FileIntegrity;
import com.android.server.utils.Slogf;
import com.android.server.utils.Snappable;
import com.android.server.utils.SnapshotCache;
@@ -2714,8 +2714,8 @@
}
try {
- FileIntegrityLocal.setUpFsVerity(mSettingsFilename.getAbsolutePath());
- FileIntegrityLocal.setUpFsVerity(mSettingsReserveCopyFilename.getAbsolutePath());
+ FileIntegrity.setUpFsVerity(mSettingsFilename);
+ FileIntegrity.setUpFsVerity(mSettingsReserveCopyFilename);
} catch (IOException e) {
Slog.e(TAG, "Failed to verity-protect settings", e);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 26a990c..a9edce1 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -553,11 +553,12 @@
* switched to.
*
* <p>Otherwise, in {@link UserManager#isHeadlessSystemUserMode() headless system user mode},
- * this will be the user who was last in the foreground on this device. If there is no
- * switchable user on the device, a new user will be created and its id will be returned.
+ * this will be the user who was last in the foreground on this device.
*
- * <p>In non-headless system user mode, the return value will be {@link UserHandle#USER_SYSTEM}.
+ * <p>In non-headless system user mode, the return value will be
+ * {@link android.os.UserHandle#USER_SYSTEM}.
+
+ * @throws UserManager.CheckedUserOperationException if no switchable user can be found
*/
- public abstract @UserIdInt int getBootUser()
- throws UserManager.CheckedUserOperationException;
+ public abstract @UserIdInt int getBootUser() throws UserManager.CheckedUserOperationException;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 762d1f6..ce7dc5b 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5049,6 +5049,8 @@
//...then external ones
Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
addedIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ // In HSUM, MainUser might be created before PHASE_ACTIVITY_MANAGER_READY has been sent.
+ addedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
// Also, add the UserHandle for mainline modules which can't use the @hide
// EXTRA_USER_HANDLE.
@@ -6758,18 +6760,6 @@
return mLocalService.isUserInitialized(userId);
}
- /**
- * Creates a new user, intended to be the initial user on a device in headless system user mode.
- */
- private UserInfo createInitialUserForHsum() throws UserManager.CheckedUserOperationException {
- final int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN;
-
- // Null name will be replaced with "Owner" on-demand to allow for localisation.
- return createUserInternalUnchecked(/* name= */ null, UserManager.USER_TYPE_FULL_SECONDARY,
- flags, UserHandle.USER_NULL, /* preCreate= */ false,
- /* disallowedPackages= */ null, /* token= */ null);
- }
-
private class LocalService extends UserManagerInternal {
@Override
public void setDevicePolicyUserRestrictions(@UserIdInt int originatingUserId,
@@ -7249,15 +7239,9 @@
}
}
}
- // No switchable users. Create the initial user.
- final UserInfo newInitialUser = createInitialUserForHsum();
- if (newInitialUser == null) {
- throw new UserManager.CheckedUserOperationException(
- "Initial user creation failed", USER_OPERATION_ERROR_UNKNOWN);
- }
- Slogf.i(LOG_TAG,
- "No switchable users. Boot user is new user %d", newInitialUser.id);
- return newInitialUser.id;
+ // No switchable users found. Uh oh!
+ throw new UserManager.CheckedUserOperationException(
+ "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
}
// Not HSUM, return system user.
return UserHandle.USER_SYSTEM;
@@ -7437,14 +7421,14 @@
/**
* Returns true, when user has {@link UserInfo#FLAG_MAIN} and system property
- * {@link com.android.internal.R.bool.isMainUserPermanentAdmin} is true.
+ * {@link com.android.internal.R.bool#config_isMainUserPermanentAdmin} is true.
*/
private boolean isNonRemovableMainUser(UserInfo userInfo) {
return userInfo.isMain() && isMainUserPermanentAdmin();
}
/**
- * Returns true, when {@link com.android.internal.R.bool.isMainUserPermanentAdmin} is true.
+ * Returns true if {@link com.android.internal.R.bool#config_isMainUserPermanentAdmin} is true.
* If the main user is a permanent admin user it can't be deleted
* or downgraded to non-admin status.
*/
diff --git a/services/core/java/com/android/server/security/FileIntegrityLocal.java b/services/core/java/com/android/server/security/FileIntegrity.java
similarity index 63%
rename from services/core/java/com/android/server/security/FileIntegrityLocal.java
rename to services/core/java/com/android/server/security/FileIntegrity.java
index 8c7219b..7b87d99 100644
--- a/services/core/java/com/android/server/security/FileIntegrityLocal.java
+++ b/services/core/java/com/android/server/security/FileIntegrity.java
@@ -18,19 +18,22 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.os.ParcelFileDescriptor;
import com.android.internal.security.VerityUtils;
+import java.io.File;
import java.io.IOException;
+
/**
* In-process API for server side FileIntegrity related infrastructure.
*
* @hide
*/
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-public final class FileIntegrityLocal {
- private FileIntegrityLocal() {}
+public final class FileIntegrity {
+ private FileIntegrity() {}
/**
* Enables fs-verity, if supported by the filesystem.
@@ -38,7 +41,18 @@
* @hide
*/
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
- public static void setUpFsVerity(@NonNull String filePath) throws IOException {
- VerityUtils.setUpFsverity(filePath);
+ public static void setUpFsVerity(@NonNull File file) throws IOException {
+ VerityUtils.setUpFsverity(file.getAbsolutePath());
+ }
+
+ /**
+ * Enables fs-verity, if supported by the filesystem.
+ * @see <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html">
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+ public static void setUpFsVerity(@NonNull ParcelFileDescriptor parcelFileDescriptor)
+ throws IOException {
+ VerityUtils.setUpFsverity(parcelFileDescriptor.getFd());
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 5fd57e3..541e0d8 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3986,6 +3986,56 @@
args, callback, resultReceiver);
}
+ private void dumpWallpaper(WallpaperData wallpaper, PrintWriter pw) {
+ pw.print(" User "); pw.print(wallpaper.userId);
+ pw.print(": id="); pw.print(wallpaper.wallpaperId);
+ pw.print(": mWhich="); pw.print(wallpaper.mWhich);
+ pw.print(": mSystemWasBoth="); pw.println(wallpaper.mSystemWasBoth);
+ pw.println(" Display state:");
+ mWallpaperDisplayHelper.forEachDisplayData(wpSize -> {
+ pw.print(" displayId=");
+ pw.println(wpSize.mDisplayId);
+ pw.print(" mWidth=");
+ pw.print(wpSize.mWidth);
+ pw.print(" mHeight=");
+ pw.println(wpSize.mHeight);
+ pw.print(" mPadding="); pw.println(wpSize.mPadding);
+ });
+ pw.print(" mCropHint="); pw.println(wallpaper.cropHint);
+ pw.print(" mName="); pw.println(wallpaper.name);
+ pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup);
+ pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent);
+ pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount);
+ pw.print(" isColorExtracted="); pw.println(wallpaper.mIsColorExtractedFromDim);
+ pw.println(" mUidToDimAmount:");
+ for (Map.Entry<Integer, Float> entry : wallpaper.mUidToDimAmount.entrySet()) {
+ pw.print(" UID="); pw.print(entry.getKey());
+ pw.print(" dimAmount="); pw.println(entry.getValue());
+ }
+ if (wallpaper.connection != null) {
+ WallpaperConnection conn = wallpaper.connection;
+ pw.print(" Wallpaper connection ");
+ pw.print(conn);
+ pw.println(":");
+ if (conn.mInfo != null) {
+ pw.print(" mInfo.component=");
+ pw.println(conn.mInfo.getComponent());
+ }
+ conn.forEachDisplayConnector(connector -> {
+ pw.print(" mDisplayId=");
+ pw.println(connector.mDisplayId);
+ pw.print(" mToken=");
+ pw.println(connector.mToken);
+ pw.print(" mEngine=");
+ pw.println(connector.mEngine);
+ });
+ pw.print(" mService=");
+ pw.println(conn.mService);
+ pw.print(" mLastDiedTime=");
+ pw.println(wallpaper.lastDiedTime - SystemClock.uptimeMillis());
+ }
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -3996,90 +4046,15 @@
synchronized (mLock) {
pw.println("System wallpaper state:");
for (int i = 0; i < mWallpaperMap.size(); i++) {
- WallpaperData wallpaper = mWallpaperMap.valueAt(i);
- pw.print(" User "); pw.print(wallpaper.userId);
- pw.print(": id="); pw.println(wallpaper.wallpaperId);
- pw.println(" Display state:");
- mWallpaperDisplayHelper.forEachDisplayData(wpSize -> {
- pw.print(" displayId=");
- pw.println(wpSize.mDisplayId);
- pw.print(" mWidth=");
- pw.print(wpSize.mWidth);
- pw.print(" mHeight=");
- pw.println(wpSize.mHeight);
- pw.print(" mPadding="); pw.println(wpSize.mPadding);
- });
- pw.print(" mCropHint="); pw.println(wallpaper.cropHint);
- pw.print(" mName="); pw.println(wallpaper.name);
- pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup);
- pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent);
- pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount);
- pw.print(" isColorExtracted="); pw.println(wallpaper.mIsColorExtractedFromDim);
- pw.println(" mUidToDimAmount:");
- for (Map.Entry<Integer, Float> entry : wallpaper.mUidToDimAmount.entrySet()) {
- pw.print(" UID="); pw.print(entry.getKey());
- pw.print(" dimAmount="); pw.println(entry.getValue());
- }
- if (wallpaper.connection != null) {
- WallpaperConnection conn = wallpaper.connection;
- pw.print(" Wallpaper connection ");
- pw.print(conn);
- pw.println(":");
- if (conn.mInfo != null) {
- pw.print(" mInfo.component=");
- pw.println(conn.mInfo.getComponent());
- }
- conn.forEachDisplayConnector(connector -> {
- pw.print(" mDisplayId=");
- pw.println(connector.mDisplayId);
- pw.print(" mToken=");
- pw.println(connector.mToken);
- pw.print(" mEngine=");
- pw.println(connector.mEngine);
- });
- pw.print(" mService=");
- pw.println(conn.mService);
- pw.print(" mLastDiedTime=");
- pw.println(wallpaper.lastDiedTime - SystemClock.uptimeMillis());
- }
+ dumpWallpaper(mWallpaperMap.valueAt(i), pw);
}
pw.println("Lock wallpaper state:");
for (int i = 0; i < mLockWallpaperMap.size(); i++) {
- WallpaperData wallpaper = mLockWallpaperMap.valueAt(i);
- pw.print(" User "); pw.print(wallpaper.userId);
- pw.print(": id="); pw.println(wallpaper.wallpaperId);
- pw.print(" mCropHint="); pw.println(wallpaper.cropHint);
- pw.print(" mName="); pw.println(wallpaper.name);
- pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup);
- pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount);
+ dumpWallpaper(mLockWallpaperMap.valueAt(i), pw);
}
pw.println("Fallback wallpaper state:");
- pw.print(" User "); pw.print(mFallbackWallpaper.userId);
- pw.print(": id="); pw.println(mFallbackWallpaper.wallpaperId);
- pw.print(" mCropHint="); pw.println(mFallbackWallpaper.cropHint);
- pw.print(" mName="); pw.println(mFallbackWallpaper.name);
- pw.print(" mAllowBackup="); pw.println(mFallbackWallpaper.allowBackup);
- if (mFallbackWallpaper.connection != null) {
- WallpaperConnection conn = mFallbackWallpaper.connection;
- pw.print(" Fallback Wallpaper connection ");
- pw.print(conn);
- pw.println(":");
- if (conn.mInfo != null) {
- pw.print(" mInfo.component=");
- pw.println(conn.mInfo.getComponent());
- }
- conn.forEachDisplayConnector(connector -> {
- pw.print(" mDisplayId=");
- pw.println(connector.mDisplayId);
- pw.print(" mToken=");
- pw.println(connector.mToken);
- pw.print(" mEngine=");
- pw.println(connector.mEngine);
- });
- pw.print(" mService=");
- pw.println(conn.mService);
- pw.print(" mLastDiedTime=");
- pw.println(mFallbackWallpaper.lastDiedTime - SystemClock.uptimeMillis());
+ if (mFallbackWallpaper != null) {
+ dumpWallpaper(mFallbackWallpaper, pw);
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 2f82167..80f660e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4748,16 +4748,27 @@
updateResumedAppTrace(r);
mLastResumedActivity = r;
- final boolean changed = r.mDisplayContent.setFocusedApp(r);
- if (changed) {
- mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
- true /*updateInputWindows*/);
- }
- if (prevTask == null || task != prevTask) {
- if (prevTask != null) {
- mTaskChangeNotificationController.notifyTaskFocusChanged(prevTask.mTaskId, false);
+ // Don't take focus when transient launching. We don't want the app to know anything
+ // until we've committed to the gesture. The focus will be transferred at the end of
+ // the transition (if the transient launch is committed) or early if explicitly requested
+ // via `setFocused*`.
+ if (!getTransitionController().isTransientCollect(r)) {
+ final Task prevFocusTask = r.mDisplayContent.mFocusedApp != null
+ ? r.mDisplayContent.mFocusedApp.getTask() : null;
+ final boolean changed = r.mDisplayContent.setFocusedApp(r);
+ if (changed) {
+ mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+ true /*updateInputWindows*/);
}
- mTaskChangeNotificationController.notifyTaskFocusChanged(task.mTaskId, true);
+ if (task != prevFocusTask) {
+ if (prevFocusTask != null) {
+ mTaskChangeNotificationController.notifyTaskFocusChanged(
+ prevFocusTask.mTaskId, false);
+ }
+ mTaskChangeNotificationController.notifyTaskFocusChanged(task.mTaskId, true);
+ }
+ }
+ if (task != prevTask) {
mTaskSupervisor.mRecentTasks.add(task);
}
diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java
index 7d9a4ec..3f28522 100644
--- a/services/core/java/com/android/server/wm/DeviceStateController.java
+++ b/services/core/java/com/android/server/wm/DeviceStateController.java
@@ -24,6 +24,7 @@
import android.os.HandlerExecutor;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
@@ -48,9 +49,12 @@
private final int[] mRearDisplayDeviceStates;
@NonNull
private final int[] mReverseRotationAroundZAxisStates;
+ @GuardedBy("this")
@NonNull
private final List<Consumer<DeviceState>> mDeviceStateCallbacks = new ArrayList<>();
+ private final boolean mMatchBuiltInDisplayOrientationToDefaultDisplay;
+
@Nullable
private DeviceState mLastDeviceState;
private int mCurrentState;
@@ -72,20 +76,19 @@
.getIntArray(R.array.config_rearDisplayDeviceStates);
mReverseRotationAroundZAxisStates = context.getResources()
.getIntArray(R.array.config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis);
+ mMatchBuiltInDisplayOrientationToDefaultDisplay = context.getResources()
+ .getBoolean(R.bool
+ .config_matchSecondaryInternalDisplaysOrientationToReverseDefaultDisplay);
if (mDeviceStateManager != null) {
mDeviceStateManager.registerCallback(new HandlerExecutor(handler), this);
}
}
- void unregisterFromDeviceStateManager() {
- if (mDeviceStateManager != null) {
- mDeviceStateManager.unregisterCallback(this);
- }
- }
-
void registerDeviceStateCallback(@NonNull Consumer<DeviceState> callback) {
- mDeviceStateCallbacks.add(callback);
+ synchronized (this) {
+ mDeviceStateCallbacks.add(callback);
+ }
}
/**
@@ -95,6 +98,15 @@
return ArrayUtils.contains(mReverseRotationAroundZAxisStates, mCurrentState);
}
+ /**
+ * @return true if non-default built-in displays should match the default display's rotation.
+ */
+ boolean shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay() {
+ // TODO(b/265991392): This should come from display_settings.xml once it's easier to
+ // extend with complex configurations.
+ return mMatchBuiltInDisplayOrientationToDefaultDisplay;
+ }
+
@Override
public void onStateChanged(int state) {
mCurrentState = state;
@@ -115,8 +127,10 @@
if (mLastDeviceState == null || !mLastDeviceState.equals(deviceState)) {
mLastDeviceState = deviceState;
- for (Consumer<DeviceState> callback : mDeviceStateCallbacks) {
- callback.accept(mLastDeviceState);
+ synchronized (this) {
+ for (Consumer<DeviceState> callback : mDeviceStateCallbacks) {
+ callback.accept(mLastDeviceState);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index de63191..c2ddb41 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -246,7 +247,22 @@
|| orientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
return false;
}
- return getIgnoreOrientationRequest();
+ return getIgnoreOrientationRequest()
+ && !shouldRespectOrientationRequestDueToPerAppOverride();
+ }
+
+ private boolean shouldRespectOrientationRequestDueToPerAppOverride() {
+ if (mDisplayContent == null) {
+ return false;
+ }
+ ActivityRecord activity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ return activity != null && activity.getTaskFragment() != null
+ // Checking TaskFragment rather than ActivityRecord to ensure that transition
+ // between fullscreen and PiP would work well. Checking TaskFragment rather than
+ // Task to ensure that Activity Embedding is excluded.
+ && activity.getTaskFragment().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && activity.mLetterboxUiController.isOverrideRespectRequestedOrientationEnabled();
}
boolean getIgnoreOrientationRequest() {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4174bfe..faed11b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -595,7 +595,8 @@
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
- private final DeviceStateController mDeviceStateController;
+ @VisibleForTesting
+ final DeviceStateController mDeviceStateController;
private final PhysicalDisplaySwitchTransitionLauncher mDisplaySwitchTransitionLauncher;
final RemoteDisplayChangeController mRemoteDisplayChangeController;
@@ -1091,7 +1092,8 @@
* @param display May not be null.
* @param root {@link RootWindowContainer}
*/
- DisplayContent(Display display, RootWindowContainer root) {
+ DisplayContent(Display display, RootWindowContainer root,
+ @NonNull DeviceStateController deviceStateController) {
super(root.mWindowManager, "DisplayContent", FEATURE_ROOT);
if (mWmService.mRoot.getDisplayContent(display.getDisplayId()) != null) {
throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
@@ -1151,11 +1153,11 @@
mWmService.mAtmService.getRecentTasks().getInputListener());
}
- mDeviceStateController = new DeviceStateController(mWmService.mContext, mWmService.mH);
+ mDeviceStateController = deviceStateController;
mDisplayPolicy = new DisplayPolicy(mWmService, this);
mDisplayRotation = new DisplayRotation(mWmService, this, mDisplayInfo.address,
- mDeviceStateController);
+ mDeviceStateController, root.getDisplayRotationCoordinator());
final Consumer<DeviceStateController.DeviceState> deviceStateConsumer =
(@NonNull DeviceStateController.DeviceState newFoldState) -> {
@@ -2163,6 +2165,10 @@
w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
}, true /* traverseTopToBottom */);
mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation);
+ if (!mDisplayRotation.hasSeamlessRotatingWindow()) {
+ // Make sure DisplayRotation#isRotatingSeamlessly() will return false.
+ mDisplayRotation.cancelSeamlessRotation();
+ }
}
mWmService.mDisplayManagerInternal.performTraversal(transaction);
@@ -3322,7 +3328,7 @@
mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
handleAnimatingStoppedAndTransition();
mWmService.stopFreezingDisplayLocked();
- mDeviceStateController.unregisterFromDeviceStateManager();
+ mDisplayRotation.removeDefaultDisplayRotationChangedCallback();
super.removeImmediately();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
mPointerEventDispatcher.dispose();
@@ -3804,6 +3810,17 @@
*/
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,
int topFocusedDisplayId) {
+ // Don't re-assign focus automatically away from a should-keep-focus app window.
+ // `findFocusedWindow` will always grab the transient-launch app since it is "on top" which
+ // would create a mismatch, so just early-out here.
+ if (mCurrentFocus != null && mTransitionController.shouldKeepFocus(mCurrentFocus)
+ // This is only keeping focus, so don't early-out if the focused-app has been
+ // explicitly changed (eg. via setFocusedTask).
+ && mFocusedApp != null && mCurrentFocus.isDescendantOf(mFocusedApp)
+ && mCurrentFocus.isVisible() && mCurrentFocus.isFocusable()) {
+ ProtoLog.v(WM_DEBUG_FOCUS, "Current transition prevents automatic focus change");
+ return false;
+ }
WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
if (mCurrentFocus == newFocus) {
return false;
@@ -4927,7 +4944,7 @@
mInsetsStateController.getImeSourceProvider().checkShowImePostLayout();
mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent;
- if (!mWmService.mDisplayFrozen) {
+ if (!mWmService.mDisplayFrozen && !mDisplayRotation.isRotatingSeamlessly()) {
mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
mLastHasContent,
mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 3404279..7071aa7 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -58,6 +58,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
+import android.util.RotationUtils;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -120,6 +121,11 @@
private FoldController mFoldController;
@NonNull
private final DeviceStateController mDeviceStateController;
+ @NonNull
+ private final DisplayRotationCoordinator mDisplayRotationCoordinator;
+ @NonNull
+ @VisibleForTesting
+ final Runnable mDefaultDisplayRotationChangedCallback;
@ScreenOrientation
private int mCurrentAppOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
@@ -221,17 +227,19 @@
private boolean mDemoRotationLock;
DisplayRotation(WindowManagerService service, DisplayContent displayContent,
- DisplayAddress displayAddress, @NonNull DeviceStateController deviceStateController) {
+ DisplayAddress displayAddress, @NonNull DeviceStateController deviceStateController,
+ @NonNull DisplayRotationCoordinator displayRotationCoordinator) {
this(service, displayContent, displayAddress, displayContent.getDisplayPolicy(),
service.mDisplayWindowSettings, service.mContext, service.getWindowManagerLock(),
- deviceStateController);
+ deviceStateController, displayRotationCoordinator);
}
@VisibleForTesting
DisplayRotation(WindowManagerService service, DisplayContent displayContent,
DisplayAddress displayAddress, DisplayPolicy displayPolicy,
DisplayWindowSettings displayWindowSettings, Context context, Object lock,
- @NonNull DeviceStateController deviceStateController) {
+ @NonNull DeviceStateController deviceStateController,
+ @NonNull DisplayRotationCoordinator displayRotationCoordinator) {
mService = service;
mDisplayContent = displayContent;
mDisplayPolicy = displayPolicy;
@@ -252,6 +260,19 @@
int defaultRotation = readDefaultDisplayRotation(displayAddress);
mRotation = defaultRotation;
+ mDisplayRotationCoordinator = displayRotationCoordinator;
+ if (isDefaultDisplay) {
+ mDisplayRotationCoordinator.setDefaultDisplayDefaultRotation(mRotation);
+ }
+ mDefaultDisplayRotationChangedCallback = this::updateRotationAndSendNewConfigIfChanged;
+
+ if (DisplayRotationCoordinator.isSecondaryInternalDisplay(displayContent)
+ && mDeviceStateController
+ .shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()) {
+ mDisplayRotationCoordinator.setDefaultDisplayRotationChangedCallback(
+ mDefaultDisplayRotationChangedCallback);
+ }
+
if (isDefaultDisplay) {
final Handler uiHandler = UiThread.getHandler();
mOrientationListener =
@@ -494,8 +515,11 @@
return false;
}
+ @Surface.Rotation
final int oldRotation = mRotation;
+ @ScreenOrientation
final int lastOrientation = mLastOrientation;
+ @Surface.Rotation
int rotation = rotationForOrientation(lastOrientation, oldRotation);
// Use the saved rotation for tabletop mode, if set.
if (mFoldController != null && mFoldController.shouldRevertOverriddenRotation()) {
@@ -507,6 +531,14 @@
Surface.rotationToString(oldRotation),
Surface.rotationToString(prevRotation));
}
+
+ if (DisplayRotationCoordinator.isSecondaryInternalDisplay(mDisplayContent)
+ && mDeviceStateController
+ .shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()) {
+ rotation = RotationUtils.reverseRotationDirectionAroundZAxis(
+ mDisplayRotationCoordinator.getDefaultDisplayCurrentRotation());
+ }
+
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and "
+ "oldRotation=%s (%d)",
@@ -525,6 +557,10 @@
return false;
}
+ if (isDefaultDisplay) {
+ mDisplayRotationCoordinator.onDefaultDisplayRotationChanged(rotation);
+ }
+
// Preemptively cancel the running recents animation -- SysUI can't currently handle this
// case properly since the signals it receives all happen post-change. We do this earlier
// in the rotation flow, since DisplayContent.updateDisplayOverrideConfigurationLocked seems
@@ -1142,17 +1178,12 @@
return mUserRotation;
}
+ @Surface.Rotation
int sensorRotation = mOrientationListener != null
? mOrientationListener.getProposedRotation() // may be -1
: -1;
if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
- // Flipping 270 and 90 has the same effect as changing the direction which rotation is
- // applied.
- if (sensorRotation == Surface.ROTATION_90) {
- sensorRotation = Surface.ROTATION_270;
- } else if (sensorRotation == Surface.ROTATION_270) {
- sensorRotation = Surface.ROTATION_90;
- }
+ sensorRotation = RotationUtils.reverseRotationDirectionAroundZAxis(sensorRotation);
}
mLastSensorRotation = sensorRotation;
if (sensorRotation < 0) {
@@ -1167,6 +1198,7 @@
final boolean deskDockEnablesAccelerometer =
mDisplayPolicy.isDeskDockEnablesAccelerometer();
+ @Surface.Rotation
final int preferredRotation;
if (!isDefaultDisplay) {
// For secondary displays we ignore things like displays sensors, docking mode and
@@ -1536,6 +1568,12 @@
return shouldUpdateRotation;
}
+ void removeDefaultDisplayRotationChangedCallback() {
+ if (DisplayRotationCoordinator.isSecondaryInternalDisplay(mDisplayContent)) {
+ mDisplayRotationCoordinator.removeDefaultDisplayRotationChangedCallback();
+ }
+ }
+
void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "DisplayRotation");
pw.println(prefix + " mCurrentAppOrientation="
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java b/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
new file mode 100644
index 0000000..ae3787c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayRotationCoordinator.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.server.wm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.Display;
+import android.view.Surface;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Singleton for coordinating rotation across multiple displays. Used to notify non-default
+ * displays when the default display rotates.
+ *
+ * Note that this class does not need locking because it is always protected by WindowManagerService
+ * mGlobalLock.
+ */
+class DisplayRotationCoordinator {
+
+ private static final String TAG = "DisplayRotationCoordinator";
+
+ @Surface.Rotation
+ private int mDefaultDisplayDefaultRotation;
+
+ @Nullable
+ @VisibleForTesting
+ Runnable mDefaultDisplayRotationChangedCallback;
+
+ @Surface.Rotation
+ private int mDefaultDisplayCurrentRotation;
+
+ /**
+ * Notifies clients when the default display rotation changes.
+ */
+ void onDefaultDisplayRotationChanged(@Surface.Rotation int rotation) {
+ mDefaultDisplayCurrentRotation = rotation;
+
+ if (mDefaultDisplayRotationChangedCallback != null) {
+ mDefaultDisplayRotationChangedCallback.run();
+ }
+ }
+
+ void setDefaultDisplayDefaultRotation(@Surface.Rotation int rotation) {
+ mDefaultDisplayDefaultRotation = rotation;
+ }
+
+ @Surface.Rotation
+ int getDefaultDisplayCurrentRotation() {
+ return mDefaultDisplayCurrentRotation;
+ }
+
+ /**
+ * Register a callback to be notified when the default display's rotation changes. Clients can
+ * query the default display's current rotation via {@link #getDefaultDisplayCurrentRotation()}.
+ */
+ void setDefaultDisplayRotationChangedCallback(@NonNull Runnable callback) {
+ if (mDefaultDisplayRotationChangedCallback != null) {
+ throw new UnsupportedOperationException("Multiple clients unsupported");
+ }
+
+ mDefaultDisplayRotationChangedCallback = callback;
+
+ if (mDefaultDisplayCurrentRotation != mDefaultDisplayDefaultRotation) {
+ callback.run();
+ }
+ }
+
+ /**
+ * Removes the callback that was added via
+ * {@link #setDefaultDisplayRotationChangedCallback(Runnable)}.
+ */
+ void removeDefaultDisplayRotationChangedCallback() {
+ mDefaultDisplayRotationChangedCallback = null;
+ }
+
+ static boolean isSecondaryInternalDisplay(@NonNull DisplayContent displayContent) {
+ if (displayContent.isDefaultDisplay) {
+ return false;
+ } else if (displayContent.mDisplay == null) {
+ return false;
+ }
+ return displayContent.mDisplay.getType() == Display.TYPE_INTERNAL;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 3e1105b..8c59548 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -405,8 +405,12 @@
// Apply recents input consumer when the focusing window is in recents animation.
final boolean shouldApplyRecentsInputConsumer = (recentsAnimationController != null
&& recentsAnimationController.shouldApplyInputConsumer(focus.mActivityRecord))
- // Shell transitions doesn't use RecentsAnimationController
- || getWeak(mActiveRecentsActivity) != null && focus.inTransition();
+ // Shell transitions doesn't use RecentsAnimationController but we still
+ // have carryover legacy logic that relies on the consumer.
+ || (getWeak(mActiveRecentsActivity) != null && focus.inTransition()
+ // only take focus from the recents activity to avoid intercepting
+ // events before the gesture officially starts.
+ && focus.isActivityTypeHomeOrRecents());
if (shouldApplyRecentsInputConsumer) {
if (mInputFocus != recentsAnimationInputConsumer.mWindowHandle.token) {
requestFocus(recentsAnimationInputConsumer.mWindowHandle.token,
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 8d6de58..9681789 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -25,6 +25,7 @@
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
+import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
@@ -143,6 +144,8 @@
private final boolean mIsOverrideOrientationOnlyForCameraEnabled;
// Corresponds to OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION
private final boolean mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled;
+ // Corresponds to OVERRIDE_RESPECT_REQUESTED_ORIENTATION
+ private final boolean mIsOverrideRespectRequestedOrientationEnabled;
// Corresponds to OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION
private final boolean mIsOverrideCameraCompatDisableForceRotationEnabled;
@@ -272,6 +275,8 @@
isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION);
+ mIsOverrideRespectRequestedOrientationEnabled =
+ isCompatChangeEnabled(OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
mIsOverrideCameraCompatDisableForceRotationEnabled =
isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION);
@@ -418,6 +423,10 @@
mIsRefreshAfterRotationRequested = isRequested;
}
+ boolean isOverrideRespectRequestedOrientationEnabled() {
+ return mIsOverrideRespectRequestedOrientationEnabled;
+ }
+
/**
* Whether should fix display orientation to landscape natural orientation when a task is
* fullscreen and the display is ignoring orientation requests.
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index de42c55..f952adb 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -59,6 +59,8 @@
}
}
+ private final DisplayInfo mDisplayInfo;
+ private final Mode mDefaultMode;
private final Mode mLowRefreshRateMode;
private final PackageRefreshRate mNonHighRefreshRatePackages = new PackageRefreshRate();
private final HighRefreshRateDenylist mHighRefreshRateDenylist;
@@ -89,7 +91,9 @@
RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
HighRefreshRateDenylist denylist) {
- mLowRefreshRateMode = findLowRefreshRateMode(displayInfo);
+ mDisplayInfo = displayInfo;
+ mDefaultMode = displayInfo.getDefaultMode();
+ mLowRefreshRateMode = findLowRefreshRateMode(displayInfo, mDefaultMode);
mHighRefreshRateDenylist = denylist;
mWmService = wmService;
}
@@ -98,10 +102,9 @@
* Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the
* default mode.
*/
- private Mode findLowRefreshRateMode(DisplayInfo displayInfo) {
- Mode mode = displayInfo.getDefaultMode();
+ private Mode findLowRefreshRateMode(DisplayInfo displayInfo, Mode defaultMode) {
float[] refreshRates = displayInfo.getDefaultRefreshRates();
- float bestRefreshRate = mode.getRefreshRate();
+ float bestRefreshRate = defaultMode.getRefreshRate();
mMinSupportedRefreshRate = bestRefreshRate;
mMaxSupportedRefreshRate = bestRefreshRate;
for (int i = refreshRates.length - 1; i >= 0; i--) {
@@ -127,13 +130,39 @@
}
int getPreferredModeId(WindowState w) {
- // If app is animating, it's not able to control refresh rate because we want the animation
- // to run in default refresh rate.
- if (w.isAnimating(TRANSITION | PARENTS)) {
+ final int preferredDisplayModeId = w.mAttrs.preferredDisplayModeId;
+ if (preferredDisplayModeId <= 0) {
+ // Unspecified, use default mode.
return 0;
}
- return w.mAttrs.preferredDisplayModeId;
+ // If app is animating, it's not able to control refresh rate because we want the animation
+ // to run in default refresh rate. But if the display size of default mode is different
+ // from the using preferred mode, then still keep the preferred mode to avoid disturbing
+ // the animation.
+ if (w.isAnimating(TRANSITION | PARENTS)) {
+ Display.Mode preferredMode = null;
+ for (Display.Mode mode : mDisplayInfo.supportedModes) {
+ if (preferredDisplayModeId == mode.getModeId()) {
+ preferredMode = mode;
+ break;
+ }
+ }
+ if (preferredMode != null) {
+ final int pW = preferredMode.getPhysicalWidth();
+ final int pH = preferredMode.getPhysicalHeight();
+ if ((pW != mDefaultMode.getPhysicalWidth()
+ || pH != mDefaultMode.getPhysicalHeight())
+ && pW == mDisplayInfo.getNaturalWidth()
+ && pH == mDisplayInfo.getNaturalHeight()) {
+ // Prefer not to change display size when animating.
+ return preferredDisplayModeId;
+ }
+ }
+ return 0;
+ }
+
+ return preferredDisplayModeId;
}
/**
@@ -234,14 +263,10 @@
if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
final int preferredModeId = w.mAttrs.preferredDisplayModeId;
if (preferredModeId > 0) {
- DisplayInfo info = w.getDisplayInfo();
- if (info != null) {
- for (Display.Mode mode : info.supportedModes) {
- if (preferredModeId == mode.getModeId()) {
- return w.mFrameRateVote.update(mode.getRefreshRate(),
- Surface.FRAME_RATE_COMPATIBILITY_EXACT);
-
- }
+ for (Display.Mode mode : mDisplayInfo.supportedModes) {
+ if (preferredModeId == mode.getModeId()) {
+ return w.mFrameRateVote.update(mode.getRefreshRate(),
+ Surface.FRAME_RATE_COMPATIBILITY_EXACT);
}
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index e8aa2c8..2343906 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -136,6 +136,7 @@
import android.view.DisplayInfo;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import android.window.TaskFragmentAnimationParams;
import android.window.WindowContainerToken;
import com.android.internal.annotations.VisibleForTesting;
@@ -234,6 +235,10 @@
WindowManagerService mWindowManager;
DisplayManager mDisplayManager;
private DisplayManagerInternal mDisplayManagerInternal;
+ @NonNull
+ private final DeviceStateController mDeviceStateController;
+ @NonNull
+ private final DisplayRotationCoordinator mDisplayRotationCoordinator;
/** Reference to default display so we can quickly look it up. */
private DisplayContent mDefaultDisplay;
@@ -440,6 +445,8 @@
mTaskSupervisor = mService.mTaskSupervisor;
mTaskSupervisor.mRootWindowContainer = this;
mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl(DISPLAY_OFF_SLEEP_TOKEN_TAG);
+ mDeviceStateController = new DeviceStateController(service.mContext, service.mH);
+ mDisplayRotationCoordinator = new DisplayRotationCoordinator();
}
/**
@@ -1279,7 +1286,8 @@
final Display[] displays = mDisplayManager.getDisplays();
for (int displayNdx = 0; displayNdx < displays.length; ++displayNdx) {
final Display display = displays[displayNdx];
- final DisplayContent displayContent = new DisplayContent(display, this);
+ final DisplayContent displayContent =
+ new DisplayContent(display, this, mDeviceStateController);
addChild(displayContent, POSITION_BOTTOM);
if (displayContent.mDisplayId == DEFAULT_DISPLAY) {
mDefaultDisplay = displayContent;
@@ -1297,6 +1305,10 @@
return mDefaultDisplay;
}
+ DisplayRotationCoordinator getDisplayRotationCoordinator() {
+ return mDisplayRotationCoordinator;
+ }
+
/**
* Get the default display area on the device dedicated to app windows. This one should be used
* only as a fallback location for activity launches when no target display area is specified,
@@ -1358,7 +1370,7 @@
return null;
}
// The display hasn't been added to ActivityManager yet, create a new record now.
- displayContent = new DisplayContent(display, this);
+ displayContent = new DisplayContent(display, this, mDeviceStateController);
addChild(displayContent, POSITION_BOTTOM);
return displayContent;
}
@@ -2082,6 +2094,8 @@
return;
}
tf.resetAdjacentTaskFragment();
+ tf.setCompanionTaskFragment(null /* companionTaskFragment */);
+ tf.setAnimationParams(TaskFragmentAnimationParams.DEFAULT);
if (tf.getTopNonFinishingActivity() != null) {
// When the Task is entering picture-in-picture, we should clear all override
// from the client organizer, so the PIP activity can get the correct config
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index eb06b91..294e90b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2813,7 +2813,7 @@
void removeImmediately() {
mIsRemovalRequested = false;
resetAdjacentTaskFragment();
- cleanUp();
+ cleanUpEmbeddedTaskFragment();
final boolean shouldExecuteAppTransition =
mClearedTaskFragmentForPip && isTaskVisibleRequested();
super.removeImmediately();
@@ -2830,10 +2830,20 @@
}
/** Called on remove to cleanup. */
- private void cleanUp() {
- if (mIsEmbedded) {
- mAtmService.mWindowOrganizerController.cleanUpEmbeddedTaskFragment(this);
+ private void cleanUpEmbeddedTaskFragment() {
+ if (!mIsEmbedded) {
+ return;
}
+ mAtmService.mWindowOrganizerController.cleanUpEmbeddedTaskFragment(this);
+ final Task task = getTask();
+ if (task == null) {
+ return;
+ }
+ task.forAllLeafTaskFragments(taskFragment -> {
+ if (taskFragment.getCompanionTaskFragment() == this) {
+ taskFragment.setCompanionTaskFragment(null /* companionTaskFragment */);
+ }
+ }, false /* traverseTopToBottom */);
}
@Override
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index d2f30ce..f0c099f 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -277,6 +277,17 @@
return false;
}
+ /** @return whether `wc` is a descendent of a transient-hide window. */
+ boolean isInTransientHide(@NonNull WindowContainer wc) {
+ if (mTransientLaunches == null) return false;
+ for (int i = 0; i < mTransientLaunches.size(); ++i) {
+ if (wc.isDescendantOf(mTransientLaunches.valueAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
boolean isTransientLaunch(@NonNull ActivityRecord activity) {
return mTransientLaunches != null && mTransientLaunches.containsKey(activity);
}
@@ -825,6 +836,11 @@
&& ar.isVisible()) {
// Transient launch was committed, so report enteringAnimation
ar.mEnteringAnimation = true;
+ // Since transient launches don't automatically take focus, make sure we
+ // synchronize focus since we committed to the launch.
+ if (ar.isTopRunningActivity()) {
+ ar.moveFocusableActivityToTop("transitionFinished");
+ }
}
continue;
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 5e116ba..18788bf 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -353,11 +353,37 @@
}
/**
+ * During transient-launch, the "behind" app should retain focus during the transition unless
+ * something takes focus from it explicitly (eg. by calling ATMS.setFocusedTask or by another
+ * transition interrupting this one.
+ *
+ * The rules around this are basically: if there is exactly one active transition and `wc` is
+ * the "behind" of a transient launch, then it can retain focus.
+ */
+ boolean shouldKeepFocus(@NonNull WindowContainer wc) {
+ if (mCollectingTransition != null) {
+ if (!mPlayingTransitions.isEmpty()) return false;
+ return mCollectingTransition.isInTransientHide(wc);
+ } else if (mPlayingTransitions.size() == 1) {
+ return mPlayingTransitions.get(0).isInTransientHide(wc);
+ }
+ return false;
+ }
+
+ /**
+ * @return {@code true} if {@param ar} is part of a transient-launch activity in the
+ * collecting transition.
+ */
+ boolean isTransientCollect(@NonNull ActivityRecord ar) {
+ return mCollectingTransition != null && mCollectingTransition.isTransientLaunch(ar);
+ }
+
+ /**
* @return {@code true} if {@param ar} is part of a transient-launch activity in an active
* transition.
*/
boolean isTransientLaunch(@NonNull ActivityRecord ar) {
- if (mCollectingTransition != null && mCollectingTransition.isTransientLaunch(ar)) {
+ if (isTransientCollect(ar)) {
return true;
}
for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index abdc708..2e9c9cf 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -431,6 +431,7 @@
applyTransaction(wct, -1 /* syncId */, transition, caller);
mTransitionController.requestStartTransition(transition, null /* startTask */,
null /* remoteTransition */, null /* displayChange */);
+ transition.setAllReady();
return;
}
@@ -469,6 +470,7 @@
mTransitionController.requestStartTransition(nextTransition,
null /* startTask */, null /* remoteTransition */,
null /* displayChange */);
+ nextTransition.setAllReady();
} else {
nextTransition.abort();
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 703cb8a4..7b880fe 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3160,15 +3160,25 @@
+ (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
+ " canReceiveTouchInput=" + canReceiveTouchInput()
+ " displayIsOnTop=" + getDisplayContent().isOnTop()
- + " displayIsTrusted=" + getDisplayContent().isTrusted();
+ + " displayIsTrusted=" + getDisplayContent().isTrusted()
+ + " transitShouldKeepFocus=" + (mActivityRecord != null
+ && mTransitionController.shouldKeepFocus(mActivityRecord));
}
public boolean canReceiveKeys(boolean fromUserTouch) {
+ if (mActivityRecord != null && mTransitionController.shouldKeepFocus(mActivityRecord)) {
+ // During transient launch, the transient-hide windows are not visibleRequested
+ // or on-top but are kept focusable and thus can receive keys.
+ return true;
+ }
final boolean canReceiveKeys = isVisibleRequestedOrAdding()
&& (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
&& ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
&& (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
- && canReceiveTouchInput();
+ // can it receive touches
+ && (mActivityRecord == null || mActivityRecord.getTask() == null
+ || !mActivityRecord.getTask().getRootTask().shouldIgnoreInput());
+
if (!canReceiveKeys) {
return false;
}
@@ -3194,6 +3204,11 @@
if (mActivityRecord == null || mActivityRecord.getTask() == null) {
return true;
}
+ // During transient launch, the transient-hide windows are not visibleRequested
+ // or on-top but are kept focusable and thus can receive touch input.
+ if (mTransitionController.shouldKeepFocus(mActivityRecord)) {
+ return true;
+ }
return !mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
&& mActivityRecord.isVisibleRequested();
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index ab1badb..ef5aa60 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -477,6 +477,10 @@
minOccurs="0" maxOccurs="1">
<xs:annotation name="final"/>
</xs:element>
+ <xs:element name="refreshRateZoneProfiles" type="refreshRateZoneProfiles"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
<xs:element name="lowerBlockingZoneConfigs" type="blockingZoneConfig"
minOccurs="0" maxOccurs="1">
<xs:annotation name="final"/>
@@ -487,6 +491,22 @@
</xs:element>
</xs:complexType>
+ <xs:complexType name="refreshRateZoneProfiles">
+ <xs:sequence>
+ <xs:element name="refreshRateZoneProfile" type="refreshRateZone"
+ minOccurs="0" maxOccurs="unbounded">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="refreshRateZone">
+ <xs:attribute name="id" use="required" type="xs:string" />
+ <xs:element name="refreshRateRange" type="refreshRateRange">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:complexType>
+
<xs:complexType name="blockingZoneConfig">
<xs:element name="defaultRefreshRate" type="xs:nonNegativeInteger"
minOccurs="1" maxOccurs="1">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 3e1db4c..ed9f959 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -194,10 +194,12 @@
method public final java.math.BigInteger getDefaultRefreshRate();
method public final com.android.server.display.config.BlockingZoneConfig getHigherBlockingZoneConfigs();
method public final com.android.server.display.config.BlockingZoneConfig getLowerBlockingZoneConfigs();
+ method public final com.android.server.display.config.RefreshRateZoneProfiles getRefreshRateZoneProfiles();
method public final void setDefaultPeakRefreshRate(java.math.BigInteger);
method public final void setDefaultRefreshRate(java.math.BigInteger);
method public final void setHigherBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
method public final void setLowerBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
+ method public final void setRefreshRateZoneProfiles(com.android.server.display.config.RefreshRateZoneProfiles);
}
public class RefreshRateRange {
@@ -208,6 +210,19 @@
method public final void setMinimum(java.math.BigInteger);
}
+ public class RefreshRateZone {
+ ctor public RefreshRateZone();
+ method public String getId();
+ method public final com.android.server.display.config.RefreshRateRange getRefreshRateRange();
+ method public void setId(String);
+ method public final void setRefreshRateRange(com.android.server.display.config.RefreshRateRange);
+ }
+
+ public class RefreshRateZoneProfiles {
+ ctor public RefreshRateZoneProfiles();
+ method public final java.util.List<com.android.server.display.config.RefreshRateZone> getRefreshRateZoneProfile();
+ }
+
public class SdrHdrRatioMap {
ctor public SdrHdrRatioMap();
method @NonNull public final java.util.List<com.android.server.display.config.SdrHdrRatioPoint> getPoint();
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 78c9a54..45e10a8 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -55,5 +55,6 @@
</xs:sequence>
<xs:attribute name="enabled" type="xs:boolean" use="optional" />
<xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" />
+ <xs:attribute name="refreshRateZoneId" type="xs:string" use="optional" />
</xs:complexType>
</xs:schema>
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index 6a28d8a..2c16c37 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 getPosition();
+ method public String getRefreshRateZoneId();
method public boolean isDefaultDisplay();
method public boolean isEnabled();
method public void setAddress(java.math.BigInteger);
@@ -13,6 +14,7 @@
method public void setDefaultDisplay(boolean);
method public void setEnabled(boolean);
method public void setPosition(String);
+ method public void setRefreshRateZoneId(String);
}
public class Layout {
diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java
deleted file mode 100644
index 3d71739..0000000
--- a/services/java/com/android/server/BootUserInitializer.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server;
-
-import android.annotation.UserIdInt;
-import android.content.ContentResolver;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-
-import com.android.server.am.ActivityManagerService;
-import com.android.server.pm.UserManagerInternal;
-import com.android.server.utils.Slogf;
-import com.android.server.utils.TimingsTraceAndSlog;
-
-/**
- * Class responsible for booting the device in the proper user on headless system user mode.
- *
- */
-// TODO(b/204091126): STOPSHIP - provide proper APIs
-final class BootUserInitializer {
-
- private static final String TAG = BootUserInitializer.class.getSimpleName();
-
- // TODO(b/204091126): STOPSHIP - set to false or dynamic value
- private static final boolean DEBUG = true;
-
- private final ActivityManagerService mAms;
- private final ContentResolver mContentResolver;
-
- BootUserInitializer(ActivityManagerService am, ContentResolver contentResolver) {
- mAms = am;
- mContentResolver = contentResolver;
- }
-
- public void init(TimingsTraceAndSlog t) {
- Slogf.i(TAG, "init())");
-
- // TODO(b/204091126): in the long term, we need to decide who's reponsible for that,
- // this class or the setup wizard app
- provisionHeadlessSystemUser();
-
- unlockSystemUser(t);
-
- try {
- t.traceBegin("getBootUser");
- int bootUser = LocalServices.getService(UserManagerInternal.class).getBootUser();
- t.traceEnd();
- t.traceBegin("switchToBootUser-" + bootUser);
- switchToBootUser(bootUser);
- t.traceEnd();
- } catch (UserManager.CheckedUserOperationException e) {
- Slogf.wtf(TAG, "Failed to created boot user", e);
- }
- }
-
- /* TODO(b/261791491): STOPSHIP - SUW should be responsible for this. */
- private void provisionHeadlessSystemUser() {
- if (isDeviceProvisioned()) {
- Slogf.d(TAG, "provisionHeadlessSystemUser(): already provisioned");
- return;
- }
-
- Slogf.i(TAG, "Marking USER_SETUP_COMPLETE for system user");
- Settings.Secure.putInt(mContentResolver, Settings.Secure.USER_SETUP_COMPLETE, 1);
- Slogf.i(TAG, "Marking DEVICE_PROVISIONED for system user");
- Settings.Global.putInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED, 1);
- }
-
- private boolean isDeviceProvisioned() {
- try {
- return Settings.Global.getInt(mContentResolver,
- Settings.Global.DEVICE_PROVISIONED) == 1;
- } catch (Exception e) {
- Slogf.wtf(TAG, "DEVICE_PROVISIONED setting not found.", e);
- return false;
- }
- }
-
- // NOTE: Mostly copied from Automotive's InitialUserSetter
- private void unlockSystemUser(TimingsTraceAndSlog t) {
- Slogf.i(TAG, "Unlocking system user");
- t.traceBegin("unlock-system-user");
- try {
- // This is for force changing state into RUNNING_LOCKED. Otherwise unlock does not
- // update the state and USER_SYSTEM unlock happens twice.
- t.traceBegin("am.startUser");
- boolean started = mAms.startUserInBackgroundWithListener(UserHandle.USER_SYSTEM,
- /* listener= */ null);
- t.traceEnd();
- if (!started) {
- Slogf.w(TAG, "could not restart system user in background; trying unlock instead");
- t.traceBegin("am.unlockUser");
- boolean unlocked = mAms.unlockUser(UserHandle.USER_SYSTEM, /* token= */ null,
- /* secret= */ null, /* listener= */ null);
- t.traceEnd();
- if (!unlocked) {
- Slogf.w(TAG, "could not unlock system user either");
- return;
- }
- }
- } finally {
- t.traceEnd();
- }
- }
-
- private void switchToBootUser(@UserIdInt int bootUserId) {
- Slogf.i(TAG, "Switching to boot user %d", bootUserId);
- boolean started = mAms.startUserInForegroundWithListener(bootUserId,
- /* unlockListener= */ null);
- if (!started) {
- Slogf.wtf(TAG, "Failed to start user %d in foreground", bootUserId);
- }
- }
-}
diff --git a/services/java/com/android/server/HsumBootUserInitializer.java b/services/java/com/android/server/HsumBootUserInitializer.java
new file mode 100644
index 0000000..cc6c36e
--- /dev/null
+++ b/services/java/com/android/server/HsumBootUserInitializer.java
@@ -0,0 +1,190 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ContentResolver;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import com.android.server.am.ActivityManagerService;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.utils.Slogf;
+import com.android.server.utils.TimingsTraceAndSlog;
+
+/**
+ * Class responsible for booting the device in the proper user on headless system user mode.
+ *
+ */
+final class HsumBootUserInitializer {
+
+ private static final String TAG = HsumBootUserInitializer.class.getSimpleName();
+
+ private final UserManagerInternal mUmi;
+ private final ActivityManagerService mAms;
+ private final ContentResolver mContentResolver;
+
+ /** Whether this device should always have a non-removable MainUser, including at first boot. */
+ private final boolean mShouldAlwaysHaveMainUser;
+
+ /** Static factory method for creating a {@link HsumBootUserInitializer} instance. */
+ public static @Nullable HsumBootUserInitializer createInstance(ActivityManagerService am,
+ ContentResolver contentResolver, boolean shouldAlwaysHaveMainUser) {
+
+ if (!UserManager.isHeadlessSystemUserMode()) {
+ return null;
+ }
+ return new HsumBootUserInitializer(
+ LocalServices.getService(UserManagerInternal.class),
+ am, contentResolver, shouldAlwaysHaveMainUser);
+ }
+
+ private HsumBootUserInitializer(UserManagerInternal umi, ActivityManagerService am,
+ ContentResolver contentResolver, boolean shouldAlwaysHaveMainUser) {
+ mUmi = umi;
+ mAms = am;
+ mContentResolver = contentResolver;
+ this.mShouldAlwaysHaveMainUser = shouldAlwaysHaveMainUser;
+ }
+
+ /**
+ * Initialize this object, and create MainUser if needed.
+ *
+ * Should be called before PHASE_SYSTEM_SERVICES_READY as services' setups may require MainUser,
+ * but probably after PHASE_LOCK_SETTINGS_READY since that may be needed for user creation.
+ */
+ public void init(TimingsTraceAndSlog t) {
+ Slogf.i(TAG, "init())");
+
+ // TODO(b/204091126): in the long term, we need to decide who's reponsible for that,
+ // this class or the setup wizard app
+ provisionHeadlessSystemUser();
+
+ if (mShouldAlwaysHaveMainUser) {
+ t.traceBegin("createMainUserIfNeeded");
+ createMainUserIfNeeded();
+ t.traceEnd();
+ }
+ }
+
+ private void createMainUserIfNeeded() {
+ int mainUser = mUmi.getMainUserId();
+ if (mainUser != UserHandle.USER_NULL) {
+ Slogf.d(TAG, "Found existing MainUser, userId=%d", mainUser);
+ return;
+ }
+
+ Slogf.d(TAG, "Creating a new MainUser");
+ try {
+ final UserInfo newInitialUser = mUmi.createUserEvenWhenDisallowed(
+ /* name= */ null, // null will appear as "Owner" in on-demand localisation
+ UserManager.USER_TYPE_FULL_SECONDARY,
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN,
+ /* disallowedPackages= */ null,
+ /* token= */ null);
+ if (newInitialUser == null) {
+ Slogf.wtf(TAG, "Initial bootable MainUser creation failed: returned null");
+ } else {
+ Slogf.i(TAG, "Successfully created MainUser, userId=%d", newInitialUser.id);
+ }
+ } catch (UserManager.CheckedUserOperationException e) {
+ Slogf.wtf(TAG, "Initial bootable MainUser creation failed", e);
+ }
+ }
+
+ /**
+ * Put the device into the correct user state: unlock the system and switch to the boot user.
+ *
+ * Should only call once PHASE_THIRD_PARTY_APPS_CAN_START is reached to ensure that privileged
+ * apps have had the chance to set the boot user, if applicable.
+ */
+ public void systemRunning(TimingsTraceAndSlog t) {
+ unlockSystemUser(t);
+
+ try {
+ t.traceBegin("getBootUser");
+ final int bootUser = mUmi.getBootUser();
+ t.traceEnd();
+ t.traceBegin("switchToBootUser-" + bootUser);
+ switchToBootUser(bootUser);
+ t.traceEnd();
+ } catch (UserManager.CheckedUserOperationException e) {
+ Slogf.wtf(TAG, "Failed to switch to boot user since there isn't one.");
+ }
+ }
+
+ /* TODO(b/261791491): STOPSHIP - SUW should be responsible for this. */
+ private void provisionHeadlessSystemUser() {
+ if (isDeviceProvisioned()) {
+ Slogf.d(TAG, "provisionHeadlessSystemUser(): already provisioned");
+ return;
+ }
+
+ Slogf.i(TAG, "Marking USER_SETUP_COMPLETE for system user");
+ Settings.Secure.putInt(mContentResolver, Settings.Secure.USER_SETUP_COMPLETE, 1);
+ Slogf.i(TAG, "Marking DEVICE_PROVISIONED for system user");
+ Settings.Global.putInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED, 1);
+ }
+
+ private boolean isDeviceProvisioned() {
+ try {
+ return Settings.Global.getInt(mContentResolver,
+ Settings.Global.DEVICE_PROVISIONED) == 1;
+ } catch (Exception e) {
+ Slogf.wtf(TAG, "DEVICE_PROVISIONED setting not found.", e);
+ return false;
+ }
+ }
+
+ // NOTE: Mostly copied from Automotive's InitialUserSetter
+ private void unlockSystemUser(TimingsTraceAndSlog t) {
+ Slogf.i(TAG, "Unlocking system user");
+ t.traceBegin("unlock-system-user");
+ try {
+ // This is for force changing state into RUNNING_LOCKED. Otherwise unlock does not
+ // update the state and USER_SYSTEM unlock happens twice.
+ t.traceBegin("am.startUser");
+ boolean started = mAms.startUserInBackgroundWithListener(UserHandle.USER_SYSTEM,
+ /* listener= */ null);
+ t.traceEnd();
+ if (!started) {
+ Slogf.w(TAG, "could not restart system user in background; trying unlock instead");
+ t.traceBegin("am.unlockUser");
+ boolean unlocked = mAms.unlockUser(UserHandle.USER_SYSTEM, /* token= */ null,
+ /* secret= */ null, /* listener= */ null);
+ t.traceEnd();
+ if (!unlocked) {
+ Slogf.w(TAG, "could not unlock system user either");
+ return;
+ }
+ }
+ } finally {
+ t.traceEnd();
+ }
+ }
+
+ private void switchToBootUser(@UserIdInt int bootUserId) {
+ Slogf.i(TAG, "Switching to boot user %d", bootUserId);
+ boolean started = mAms.startUserInForegroundWithListener(bootUserId,
+ /* unlockListener= */ null);
+ if (!started) {
+ Slogf.wtf(TAG, "Failed to start user %d in foreground", bootUserId);
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a15c6d2..d22be9e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -75,7 +75,6 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -2694,6 +2693,18 @@
mSystemServiceManager.startBootPhase(t, SystemService.PHASE_LOCK_SETTINGS_READY);
t.traceEnd();
+ // Create initial user if needed, which should be done early since some system services rely
+ // on it in their setup, but likely needs to be done after LockSettingsService is ready.
+ final HsumBootUserInitializer hsumBootUserInitializer =
+ HsumBootUserInitializer.createInstance(
+ mActivityManagerService, mContentResolver,
+ context.getResources().getBoolean(R.bool.config_isMainUserPermanentAdmin));
+ if (hsumBootUserInitializer != null) {
+ t.traceBegin("HsumBootUserInitializer.init");
+ hsumBootUserInitializer.init(t);
+ t.traceEnd();
+ }
+
t.traceBegin("StartBootPhaseSystemServicesReady");
mSystemServiceManager.startBootPhase(t, SystemService.PHASE_SYSTEM_SERVICES_READY);
t.traceEnd();
@@ -2961,10 +2972,10 @@
mSystemServiceManager.startBootPhase(t, SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
t.traceEnd();
- if (UserManager.isHeadlessSystemUserMode() && !isAutomotive) {
- // TODO(b/204091126): remove isAutomotive check once the workflow is finalized
- t.traceBegin("BootUserInitializer");
- new BootUserInitializer(mActivityManagerService, mContentResolver).init(t);
+ if (hsumBootUserInitializer != null && !isAutomotive) {
+ // TODO(b/261924826): remove isAutomotive check once the workflow is finalized
+ t.traceBegin("HsumBootUserInitializer.systemRunning");
+ hsumBootUserInitializer.systemRunning(t);
t.traceEnd();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 57e873d..5ca01ee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -19,13 +19,14 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -41,6 +42,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
+import android.os.SystemProperties;
import android.os.test.TestLooper;
import android.util.FloatProperty;
import android.view.Display;
@@ -55,6 +57,7 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.testutils.OffsettableClock;
@@ -77,13 +80,18 @@
public final class DisplayPowerController2Test {
private static final String UNIQUE_DISPLAY_ID = "unique_id_test123";
private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+ private static final int FOLLOWER_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
private MockitoSession mSession;
private OffsettableClock mClock;
private TestLooper mTestLooper;
private Handler mHandler;
private DisplayPowerController2.Injector mInjector;
+ private DisplayPowerController2.Injector mFollowerInjector;
private Context mContextSpy;
+ private DisplayPowerController2 mDpc;
+ private DisplayPowerController2 mFollowerDpc;
+ private Sensor mProxSensor;
@Mock
private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
@@ -94,14 +102,22 @@
@Mock
private HighBrightnessModeMetadata mHighBrightnessModeMetadataMock;
@Mock
+ private HighBrightnessModeMetadata mFollowerHighBrightnessModeMetadataMock;
+ @Mock
private LogicalDisplay mLogicalDisplayMock;
@Mock
+ private LogicalDisplay mFollowerLogicalDisplayMock;
+ @Mock
private DisplayDevice mDisplayDeviceMock;
@Mock
+ private DisplayDevice mFollowerDisplayDeviceMock;
+ @Mock
private BrightnessTracker mBrightnessTrackerMock;
@Mock
private BrightnessSetting mBrightnessSettingMock;
@Mock
+ private BrightnessSetting mFollowerBrightnessSettingMock;
+ @Mock
private WindowManagerPolicy mWindowManagerPolicyMock;
@Mock
private PowerManager mPowerManagerMock;
@@ -110,10 +126,22 @@
@Mock
private DisplayDeviceConfig mDisplayDeviceConfigMock;
@Mock
+ private DisplayDeviceConfig mFollowerDisplayDeviceConfigMock;
+ @Mock
private DisplayPowerState mDisplayPowerStateMock;
@Mock
private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock;
@Mock
+ private DualRampAnimator<DisplayPowerState> mFollowerDualRampAnimatorMock;
+ @Mock
+ private AutomaticBrightnessController mAutomaticBrightnessControllerMock;
+ @Mock
+ private AutomaticBrightnessController mFollowerAutomaticBrightnessControllerMock;
+ @Mock
+ private BrightnessMappingStrategy mBrightnessMapperMock;
+ @Mock
+ private HysteresisLevels mHysteresisLevelsMock;
+ @Mock
private WakelockController mWakelockController;
@Mock
private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
@@ -126,6 +154,7 @@
mSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
+ .spyStatic(SystemProperties.class)
.spyStatic(LocalServices.class)
.spyStatic(BatteryStatsService.class)
.startMocking();
@@ -167,6 +196,113 @@
displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
sensorManager, /* injector= */ null);
}
+
+ @Override
+ AutomaticBrightnessController getAutomaticBrightnessController(
+ AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
+ BrightnessMappingStrategy interactiveModeBrightnessMapper,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
+ HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+ HighBrightnessModeController hbmController,
+ BrightnessThrottler brightnessThrottler,
+ BrightnessMappingStrategy idleModeBrightnessMapper,
+ int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
+ float userBrightness) {
+ return mAutomaticBrightnessControllerMock;
+ }
+
+ @Override
+ BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+ DisplayDeviceConfig displayDeviceConfig,
+ DisplayWhiteBalanceController displayWhiteBalanceController) {
+ return mBrightnessMapperMock;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
+ return mHysteresisLevelsMock;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+ return mHysteresisLevelsMock;
+ }
+ };
+ mFollowerInjector = new DisplayPowerController2.Injector() {
+ @Override
+ DisplayPowerController2.Clock getClock() {
+ return mClock::now;
+ }
+
+ @Override
+ DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+ int displayId, int displayState) {
+ return mDisplayPowerStateMock;
+ }
+
+ @Override
+ DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+ FloatProperty<DisplayPowerState> firstProperty,
+ FloatProperty<DisplayPowerState> secondProperty) {
+ return mFollowerDualRampAnimatorMock;
+ }
+
+ @Override
+ AutomaticBrightnessController getAutomaticBrightnessController(
+ AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
+ BrightnessMappingStrategy interactiveModeBrightnessMapper,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
+ HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+ HighBrightnessModeController hbmController,
+ BrightnessThrottler brightnessThrottler,
+ BrightnessMappingStrategy idleModeBrightnessMapper,
+ int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
+ float userBrightness) {
+ return mFollowerAutomaticBrightnessControllerMock;
+ }
+
+ @Override
+ BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+ DisplayDeviceConfig displayDeviceConfig,
+ DisplayWhiteBalanceController displayWhiteBalanceController) {
+ return mBrightnessMapperMock;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
+ return mHysteresisLevelsMock;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+ return mHysteresisLevelsMock;
+ }
};
addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
@@ -174,11 +310,30 @@
when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
+ SystemProperties.set(anyString(), any()));
doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
mCdsiMock).when(() -> LocalServices.getService(
ColorDisplayService.ColorDisplayServiceInternal.class));
- doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
- BatteryStatsService.getService());
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+
+ setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID, mLogicalDisplayMock, mDisplayDeviceMock,
+ mDisplayDeviceConfigMock);
+ setUpDisplay(FOLLOWER_DISPLAY_ID, UNIQUE_DISPLAY_ID, mFollowerLogicalDisplayMock,
+ mFollowerDisplayDeviceMock, mFollowerDisplayDeviceConfigMock);
+
+ mProxSensor = setUpProxSensor();
+
+ mDpc = new DisplayPowerController2(
+ mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
+ mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
+ mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
+ }, mHighBrightnessModeMetadataMock);
+ mFollowerDpc = new DisplayPowerController2(
+ mContextSpy, mFollowerInjector, mDisplayPowerCallbacksMock, mHandler,
+ mSensorManagerMock, mDisplayBlankerMock, mFollowerLogicalDisplayMock,
+ mBrightnessTrackerMock, mFollowerBrightnessSettingMock, () -> {
+ }, mFollowerHighBrightnessModeMetadataMock);
}
@After
@@ -189,30 +344,20 @@
@Test
public void testReleaseProxSuspendBlockersOnExit() throws Exception {
- setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID);
-
- Sensor proxSensor = setUpProxSensor();
-
- DisplayPowerController2 dpc = new DisplayPowerController2(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
-
when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
// send a display power request
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
dpr.useProximitySensor = true;
- dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+ mDpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
// Run updatePowerState to start listener for the prox sensor
advanceTime(1);
- SensorEventListener listener = getSensorEventListener(proxSensor);
+ SensorEventListener listener = getSensorEventListener(mProxSensor);
assertNotNull(listener);
- listener.onSensorChanged(TestUtils.createSensorEvent(proxSensor, 5 /* lux */));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, 5 /* lux */));
advanceTime(1);
// two times, one for unfinished business and one for proximity
@@ -221,8 +366,7 @@
verify(mWakelockController).acquireWakelock(
WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
-
- dpc.stop();
+ mDpc.stop();
advanceTime(1);
// two times, one for unfinished business and one for proximity
verify(mWakelockController).acquireWakelock(
@@ -232,29 +376,19 @@
}
@Test
- public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() throws Exception {
- setUpDisplay(1, UNIQUE_DISPLAY_ID);
-
- Sensor proxSensor = setUpProxSensor();
-
- DisplayPowerController2 dpc = new DisplayPowerController2(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
-
+ public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
// send a display power request
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
dpr.useProximitySensor = true;
- dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+ mFollowerDpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
// Run updatePowerState
advanceTime(1);
verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
- eq(proxSensor), anyInt(), any(Handler.class));
+ eq(mProxSensor), anyInt(), any(Handler.class));
}
/**
@@ -284,56 +418,158 @@
return mSensorEventListenerCaptor.getValue();
}
- private void setUpDisplay(int displayId, String uniqueId) {
+ private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
+ DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock) {
DisplayInfo info = new DisplayInfo();
DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
- when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
- when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock);
- when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
- when(mLogicalDisplayMock.isEnabledLocked()).thenReturn(true);
- when(mLogicalDisplayMock.isInTransitionLocked()).thenReturn(false);
- when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
- when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId);
- when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock);
- when(mDisplayDeviceConfigMock.getProximitySensor()).thenReturn(
+ when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+ when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
+ when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+ when(logicalDisplayMock.isEnabledLocked()).thenReturn(true);
+ when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
+ when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+ when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+ when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
+ when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
new DisplayDeviceConfig.SensorData() {
{
type = Sensor.STRING_TYPE_PROXIMITY;
name = null;
}
});
- when(mDisplayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+ when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+ when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
+ when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData());
+ when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData());
}
@Test
- public void testDisplayBrightnessFollowers() {
- setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID);
+ public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ mFollowerDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
- DisplayPowerController2 defaultDpc = new DisplayPowerController2(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
- DisplayPowerController2 followerDpc = new DisplayPowerController2(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mBrightnessSettingMock).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
- defaultDpc.addDisplayBrightnessFollower(followerDpc);
+ mDpc.addDisplayBrightnessFollower(mFollowerDpc);
- defaultDpc.setBrightness(0.3f);
- assertEquals(defaultDpc.getBrightnessInfo().brightness,
- followerDpc.getBrightnessInfo().brightness, 0);
+ // Test different float scale values
+ float leadBrightness = 0.3f;
+ float followerBrightness = 0.4f;
+ float nits = 300;
+ when(mAutomaticBrightnessControllerMock.convertToNits(leadBrightness)).thenReturn(nits);
+ when(mFollowerAutomaticBrightnessControllerMock.convertToFloatScale(nits))
+ .thenReturn(followerBrightness);
+ when(mBrightnessSettingMock.getBrightness()).thenReturn(leadBrightness);
+ listener.onBrightnessChanged(leadBrightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mDualRampAnimatorMock).animateTo(eq(leadBrightness), anyFloat(), anyFloat());
+ verify(mFollowerDualRampAnimatorMock).animateTo(eq(followerBrightness), anyFloat(),
+ anyFloat());
- defaultDpc.setBrightness(0.6f);
- assertEquals(defaultDpc.getBrightnessInfo().brightness,
- followerDpc.getBrightnessInfo().brightness, 0);
+ clearInvocations(mDualRampAnimatorMock, mFollowerDualRampAnimatorMock);
- float brightness = 0.1f;
- defaultDpc.clearDisplayBrightnessFollowers();
- defaultDpc.setBrightness(brightness);
- assertNotEquals(brightness, followerDpc.getBrightnessInfo().brightness, 0);
+ // Test the same float scale value
+ float brightness = 0.6f;
+ nits = 600;
+ when(mAutomaticBrightnessControllerMock.convertToNits(brightness)).thenReturn(nits);
+ when(mFollowerAutomaticBrightnessControllerMock.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mBrightnessSettingMock.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(mFollowerDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
+
+ clearInvocations(mDualRampAnimatorMock, mFollowerDualRampAnimatorMock);
+
+ // Test clear followers
+ mDpc.clearDisplayBrightnessFollowers();
+ when(mBrightnessSettingMock.getBrightness()).thenReturn(leadBrightness);
+ listener.onBrightnessChanged(leadBrightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mDualRampAnimatorMock).animateTo(eq(leadBrightness), anyFloat(), anyFloat());
+ verify(mFollowerDualRampAnimatorMock, never()).animateTo(eq(followerBrightness), anyFloat(),
+ anyFloat());
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ mFollowerDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mBrightnessSettingMock).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mDpc.addDisplayBrightnessFollower(mFollowerDpc);
+
+ float brightness = 0.3f;
+ when(mAutomaticBrightnessControllerMock.convertToNits(brightness)).thenReturn(300f);
+ when(mFollowerAutomaticBrightnessControllerMock.convertToFloatScale(anyFloat()))
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mBrightnessSettingMock.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(mFollowerDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ mFollowerDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mBrightnessSettingMock).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mDpc.addDisplayBrightnessFollower(mFollowerDpc);
+
+ float brightness = 0.3f;
+ when(mAutomaticBrightnessControllerMock.convertToNits(anyFloat())).thenReturn(-1f);
+ when(mBrightnessSettingMock.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(mFollowerDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ mFollowerDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mBrightnessSettingMock).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mDpc.addDisplayBrightnessFollower(mFollowerDpc);
+
+ float brightness = 0.3f;
+ when(mAutomaticBrightnessControllerMock.convertToNits(anyFloat())).thenReturn(-1f);
+ when(mFollowerAutomaticBrightnessControllerMock.convertToFloatScale(anyFloat()))
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mBrightnessSettingMock.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(mFollowerDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index 6bf5b62..996a9ab 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -19,14 +19,14 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -40,7 +40,9 @@
import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
+import android.os.Looper;
import android.os.PowerManager;
+import android.os.SystemProperties;
import android.os.test.TestLooper;
import android.util.FloatProperty;
import android.view.Display;
@@ -55,6 +57,7 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.testutils.OffsettableClock;
@@ -77,13 +80,18 @@
public final class DisplayPowerControllerTest {
private static final String UNIQUE_DISPLAY_ID = "unique_id_test123";
private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+ private static final int FOLLOWER_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
private MockitoSession mSession;
private OffsettableClock mClock;
private TestLooper mTestLooper;
private Handler mHandler;
private DisplayPowerController.Injector mInjector;
+ private DisplayPowerController.Injector mFollowerInjector;
private Context mContextSpy;
+ private DisplayPowerController mDpc;
+ private DisplayPowerController mFollowerDpc;
+ private Sensor mProxSensor;
@Mock
private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
@@ -94,14 +102,22 @@
@Mock
private LogicalDisplay mLogicalDisplayMock;
@Mock
+ private LogicalDisplay mFollowerLogicalDisplayMock;
+ @Mock
private DisplayDevice mDisplayDeviceMock;
@Mock
+ private DisplayDevice mFollowerDisplayDeviceMock;
+ @Mock
private HighBrightnessModeMetadata mHighBrightnessModeMetadataMock;
@Mock
+ private HighBrightnessModeMetadata mFollowerHighBrightnessModeMetadataMock;
+ @Mock
private BrightnessTracker mBrightnessTrackerMock;
@Mock
private BrightnessSetting mBrightnessSettingMock;
@Mock
+ private BrightnessSetting mFollowerBrightnessSettingMock;
+ @Mock
private WindowManagerPolicy mWindowManagerPolicyMock;
@Mock
private PowerManager mPowerManagerMock;
@@ -110,10 +126,22 @@
@Mock
private DisplayDeviceConfig mDisplayDeviceConfigMock;
@Mock
+ private DisplayDeviceConfig mFollowerDisplayDeviceConfigMock;
+ @Mock
private DisplayPowerState mDisplayPowerStateMock;
@Mock
private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock;
@Mock
+ private DualRampAnimator<DisplayPowerState> mFollowerDualRampAnimatorMock;
+ @Mock
+ private AutomaticBrightnessController mAutomaticBrightnessControllerMock;
+ @Mock
+ private AutomaticBrightnessController mFollowerAutomaticBrightnessControllerMock;
+ @Mock
+ private BrightnessMappingStrategy mBrightnessMapperMock;
+ @Mock
+ private HysteresisLevels mHysteresisLevelsMock;
+ @Mock
private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
@Captor
@@ -124,6 +152,7 @@
mSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
+ .spyStatic(SystemProperties.class)
.spyStatic(LocalServices.class)
.spyStatic(BatteryStatsService.class)
.startMocking();
@@ -149,6 +178,113 @@
FloatProperty<DisplayPowerState> secondProperty) {
return mDualRampAnimatorMock;
}
+
+ @Override
+ AutomaticBrightnessController getAutomaticBrightnessController(
+ AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
+ BrightnessMappingStrategy interactiveModeBrightnessMapper,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
+ HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+ HighBrightnessModeController hbmController,
+ BrightnessThrottler brightnessThrottler,
+ BrightnessMappingStrategy idleModeBrightnessMapper,
+ int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
+ float userBrightness) {
+ return mAutomaticBrightnessControllerMock;
+ }
+
+ @Override
+ BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+ DisplayDeviceConfig displayDeviceConfig,
+ DisplayWhiteBalanceController displayWhiteBalanceController) {
+ return mBrightnessMapperMock;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
+ return mHysteresisLevelsMock;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+ return mHysteresisLevelsMock;
+ }
+ };
+ mFollowerInjector = new DisplayPowerController.Injector() {
+ @Override
+ DisplayPowerController.Clock getClock() {
+ return mClock::now;
+ }
+
+ @Override
+ DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+ int displayId, int displayState) {
+ return mDisplayPowerStateMock;
+ }
+
+ @Override
+ DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+ FloatProperty<DisplayPowerState> firstProperty,
+ FloatProperty<DisplayPowerState> secondProperty) {
+ return mFollowerDualRampAnimatorMock;
+ }
+
+ @Override
+ AutomaticBrightnessController getAutomaticBrightnessController(
+ AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
+ BrightnessMappingStrategy interactiveModeBrightnessMapper,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
+ HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+ HighBrightnessModeController hbmController,
+ BrightnessThrottler brightnessThrottler,
+ BrightnessMappingStrategy idleModeBrightnessMapper,
+ int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
+ float userBrightness) {
+ return mFollowerAutomaticBrightnessControllerMock;
+ }
+
+ @Override
+ BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+ DisplayDeviceConfig displayDeviceConfig,
+ DisplayWhiteBalanceController displayWhiteBalanceController) {
+ return mBrightnessMapperMock;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
+ return mHysteresisLevelsMock;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+ return mHysteresisLevelsMock;
+ }
};
addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
@@ -156,11 +292,30 @@
when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
+ SystemProperties.set(anyString(), any()));
doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
mCdsiMock).when(() -> LocalServices.getService(
- ColorDisplayService.ColorDisplayServiceInternal.class));
- doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
- BatteryStatsService.getService());
+ ColorDisplayService.ColorDisplayServiceInternal.class));
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+
+ setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID, mLogicalDisplayMock, mDisplayDeviceMock,
+ mDisplayDeviceConfigMock);
+ setUpDisplay(FOLLOWER_DISPLAY_ID, UNIQUE_DISPLAY_ID, mFollowerLogicalDisplayMock,
+ mFollowerDisplayDeviceMock, mFollowerDisplayDeviceConfigMock);
+
+ mProxSensor = setUpProxSensor();
+
+ mDpc = new DisplayPowerController(
+ mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
+ mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
+ mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
+ }, mHighBrightnessModeMetadataMock);
+ mFollowerDpc = new DisplayPowerController(
+ mContextSpy, mFollowerInjector, mDisplayPowerCallbacksMock, mHandler,
+ mSensorManagerMock, mDisplayBlankerMock, mFollowerLogicalDisplayMock,
+ mBrightnessTrackerMock, mFollowerBrightnessSettingMock, () -> {
+ }, mFollowerHighBrightnessModeMetadataMock);
}
@After
@@ -171,72 +326,52 @@
@Test
public void testReleaseProxSuspendBlockersOnExit() throws Exception {
- setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID);
-
- Sensor proxSensor = setUpProxSensor();
-
- DisplayPowerController dpc = new DisplayPowerController(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
-
when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
// send a display power request
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
dpr.useProximitySensor = true;
- dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+ mDpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
// Run updatePowerState to start listener for the prox sensor
advanceTime(1);
- SensorEventListener listener = getSensorEventListener(proxSensor);
+ SensorEventListener listener = getSensorEventListener(mProxSensor);
assertNotNull(listener);
- listener.onSensorChanged(TestUtils.createSensorEvent(proxSensor, 5 /* lux */));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, 5 /* lux */));
advanceTime(1);
// two times, one for unfinished business and one for proximity
verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
- dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+ mDpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
- dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+ mDpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
- dpc.stop();
+ mDpc.stop();
advanceTime(1);
// two times, one for unfinished business and one for proximity
verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
- dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+ mDpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
- dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+ mDpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
}
@Test
- public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() throws Exception {
- setUpDisplay(1, UNIQUE_DISPLAY_ID);
-
- Sensor proxSensor = setUpProxSensor();
-
- DisplayPowerController dpc = new DisplayPowerController(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
-
+ public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
// send a display power request
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
dpr.useProximitySensor = true;
- dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+ mFollowerDpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
// Run updatePowerState
advanceTime(1);
verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
- eq(proxSensor), anyInt(), any(Handler.class));
+ eq(mProxSensor), anyInt(), any(Handler.class));
}
/**
@@ -266,56 +401,158 @@
return mSensorEventListenerCaptor.getValue();
}
- private void setUpDisplay(int displayId, String uniqueId) {
+ private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
+ DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock) {
DisplayInfo info = new DisplayInfo();
DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
- when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
- when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock);
- when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
- when(mLogicalDisplayMock.isEnabledLocked()).thenReturn(true);
- when(mLogicalDisplayMock.isInTransitionLocked()).thenReturn(false);
- when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
- when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId);
- when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock);
- when(mDisplayDeviceConfigMock.getProximitySensor()).thenReturn(
+ when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+ when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
+ when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+ when(logicalDisplayMock.isEnabledLocked()).thenReturn(true);
+ when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
+ when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+ when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+ when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
+ when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
new DisplayDeviceConfig.SensorData() {
{
type = Sensor.STRING_TYPE_PROXIMITY;
name = null;
}
});
- when(mDisplayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+ when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+ when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
+ when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData());
+ when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData());
}
@Test
- public void testDisplayBrightnessFollowers() {
- setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID);
+ public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ mFollowerDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
- DisplayPowerController defaultDpc = new DisplayPowerController(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
- DisplayPowerController followerDpc = new DisplayPowerController(
- mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
- mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
- mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
- }, mHighBrightnessModeMetadataMock);
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mBrightnessSettingMock).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
- defaultDpc.addDisplayBrightnessFollower(followerDpc);
+ mDpc.addDisplayBrightnessFollower(mFollowerDpc);
- defaultDpc.setBrightness(0.3f);
- assertEquals(defaultDpc.getBrightnessInfo().brightness,
- followerDpc.getBrightnessInfo().brightness, 0);
+ // Test different float scale values
+ float leadBrightness = 0.3f;
+ float followerBrightness = 0.4f;
+ float nits = 300;
+ when(mAutomaticBrightnessControllerMock.convertToNits(leadBrightness)).thenReturn(nits);
+ when(mFollowerAutomaticBrightnessControllerMock.convertToFloatScale(nits))
+ .thenReturn(followerBrightness);
+ when(mBrightnessSettingMock.getBrightness()).thenReturn(leadBrightness);
+ listener.onBrightnessChanged(leadBrightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mDualRampAnimatorMock).animateTo(eq(leadBrightness), anyFloat(), anyFloat());
+ verify(mFollowerDualRampAnimatorMock).animateTo(eq(followerBrightness), anyFloat(),
+ anyFloat());
- defaultDpc.setBrightness(0.6f);
- assertEquals(defaultDpc.getBrightnessInfo().brightness,
- followerDpc.getBrightnessInfo().brightness, 0);
+ clearInvocations(mDualRampAnimatorMock, mFollowerDualRampAnimatorMock);
- float brightness = 0.1f;
- defaultDpc.clearDisplayBrightnessFollowers();
- defaultDpc.setBrightness(brightness);
- assertNotEquals(brightness, followerDpc.getBrightnessInfo().brightness, 0);
+ // Test the same float scale value
+ float brightness = 0.6f;
+ nits = 600;
+ when(mAutomaticBrightnessControllerMock.convertToNits(brightness)).thenReturn(nits);
+ when(mFollowerAutomaticBrightnessControllerMock.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mBrightnessSettingMock.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(mFollowerDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
+
+ clearInvocations(mDualRampAnimatorMock, mFollowerDualRampAnimatorMock);
+
+ // Test clear followers
+ mDpc.clearDisplayBrightnessFollowers();
+ when(mBrightnessSettingMock.getBrightness()).thenReturn(leadBrightness);
+ listener.onBrightnessChanged(leadBrightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mDualRampAnimatorMock).animateTo(eq(leadBrightness), anyFloat(), anyFloat());
+ verify(mFollowerDualRampAnimatorMock, never()).animateTo(eq(followerBrightness), anyFloat(),
+ anyFloat());
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ mFollowerDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mBrightnessSettingMock).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mDpc.addDisplayBrightnessFollower(mFollowerDpc);
+
+ float brightness = 0.3f;
+ when(mAutomaticBrightnessControllerMock.convertToNits(brightness)).thenReturn(300f);
+ when(mFollowerAutomaticBrightnessControllerMock.convertToFloatScale(anyFloat()))
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mBrightnessSettingMock.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(mFollowerDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ mFollowerDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mBrightnessSettingMock).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mDpc.addDisplayBrightnessFollower(mFollowerDpc);
+
+ float brightness = 0.3f;
+ when(mAutomaticBrightnessControllerMock.convertToNits(anyFloat())).thenReturn(-1f);
+ when(mBrightnessSettingMock.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(mFollowerDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ mFollowerDpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mBrightnessSettingMock).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mDpc.addDisplayBrightnessFollower(mFollowerDpc);
+
+ float brightness = 0.3f;
+ when(mAutomaticBrightnessControllerMock.convertToNits(anyFloat())).thenReturn(-1f);
+ when(mFollowerAutomaticBrightnessControllerMock.convertToFloatScale(anyFloat()))
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mBrightnessSettingMock.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(mFollowerDualRampAnimatorMock).animateTo(eq(brightness), anyFloat(), anyFloat());
}
}
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 d03d196..1ed2f78 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -20,6 +20,7 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -316,21 +317,10 @@
}
@Test
- public void testGetBootUser_Headless_UserCreatedIfOnlySystemUserExists() throws Exception {
+ public void testGetBootUser_Headless_ThrowsIfOnlySystemUserExists() throws Exception {
setSystemUserHeadless(true);
- int bootUser = mUmi.getBootUser();
-
- assertWithMessage("getStartingUser")
- .that(bootUser).isNotEqualTo(UserHandle.USER_SYSTEM);
-
- UserData newUser = mUsers.get(bootUser);
- assertWithMessage("New boot user is a full user")
- .that(newUser.info.isFull()).isTrue();
- assertWithMessage("New boot user is an admin user")
- .that(newUser.info.isAdmin()).isTrue();
- assertWithMessage("New boot user is the main user")
- .that(newUser.info.isMain()).isTrue();
+ assertThrows(UserManager.CheckedUserOperationException.class, () -> mUmi.getBootUser());
}
private void mockCurrentUser(@UserIdInt int userId) {
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 d996e37..bf23d9d 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
@@ -19,6 +19,7 @@
import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
+import static com.android.server.accessibility.magnification.MockWindowMagnificationConnection.TEST_DISPLAY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -48,6 +49,9 @@
import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
import android.view.DisplayInfo;
import android.view.MagnificationSpec;
import android.view.accessibility.MagnificationAnimationCallback;
@@ -55,6 +59,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.test.MessageCapturingHandler;
@@ -71,6 +76,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
+import org.testng.Assert;
import java.util.Locale;
@@ -93,6 +99,7 @@
static final int DISPLAY_1 = 1;
static final int DISPLAY_COUNT = 2;
static final int INVALID_DISPLAY = 2;
+ private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
final FullScreenMagnificationController.ControllerContext mMockControllerCtx =
mock(FullScreenMagnificationController.ControllerContext.class);
@@ -105,8 +112,8 @@
MagnificationInfoChangedCallback.class);
private final MessageCapturingHandler mMessageCapturingHandler = new MessageCapturingHandler(
null);
- private final MagnificationScaleProvider mScaleProvider = mock(
- MagnificationScaleProvider.class);
+ private MagnificationScaleProvider mScaleProvider;
+ private MockContentResolver mResolver;
private final ArgumentCaptor<MagnificationConfig> mConfigCaptor = ArgumentCaptor.forClass(
MagnificationConfig.class);
@@ -129,6 +136,12 @@
when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
+ mResolver = new MockContentResolver();
+ mResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mMockContext.getContentResolver()).thenReturn(mResolver);
+ Settings.Secure.putFloatForUser(mResolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
+ CURRENT_USER_ID);
initMockWindowManager();
final DisplayInfo displayInfo = new DisplayInfo();
@@ -137,6 +150,7 @@
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+ mScaleProvider = new MagnificationScaleProvider(mMockContext);
mFullScreenMagnificationController = new FullScreenMagnificationController(
mMockControllerCtx, new Object(), mRequestObserver, mScaleProvider);
}
@@ -1168,6 +1182,20 @@
verify(mRequestObserver).onImeWindowVisibilityChanged(eq(DISPLAY_0), eq(true));
}
+ @Test
+ public void persistScale_setValueWhenScaleIsOne_nothingChanged() {
+ final float persistedScale =
+ mFullScreenMagnificationController.getPersistedScale(TEST_DISPLAY);
+
+ PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ mFullScreenMagnificationController.setScale(DISPLAY_0, 1.0f, pivotPoint.x, pivotPoint.y,
+ false, SERVICE_ID_1);
+ mFullScreenMagnificationController.persistScale(TEST_DISPLAY);
+
+ Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(TEST_DISPLAY),
+ persistedScale);
+ }
+
private void setScaleToMagnifying() {
register(DISPLAY_0);
float scale = 2.0f;
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 25ad2be..d841dfc 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
@@ -274,6 +274,19 @@
}
@Test
+ public void persistScale_setValueWhenScaleIsOne_nothingChanged() {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ final float persistedScale = mWindowMagnificationManager.getPersistedScale(TEST_DISPLAY);
+
+ mWindowMagnificationManager.setScale(TEST_DISPLAY, 1.0f);
+ mWindowMagnificationManager.persistScale(TEST_DISPLAY);
+
+ assertEquals(Settings.Secure.getFloatForUser(mResolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0f,
+ CURRENT_USER_ID), persistedScale);
+ }
+
+ @Test
public void scaleSetterGetter_enabledOnTestDisplay_expectedValue() {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2.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 b146c27..18d629f 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -412,7 +412,7 @@
mInjector.mHandler.clearAllRecordedMessages();
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
- verify(mInjector, times(0)).dismissKeyguard(any(), anyString());
+ verify(mInjector, times(0)).dismissKeyguard(any());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
verifySystemUserVisibilityChangesNeverNotified();
@@ -433,7 +433,7 @@
mInjector.mHandler.clearAllRecordedMessages();
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
- verify(mInjector, times(1)).dismissKeyguard(any(), anyString());
+ verify(mInjector, times(1)).dismissKeyguard(any());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
verifySystemUserVisibilityChangesNeverNotified();
@@ -1148,7 +1148,7 @@
}
@Override
- protected void dismissKeyguard(Runnable runnable, String reason) {
+ protected void dismissKeyguard(Runnable runnable) {
runnable.run();
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index a192913..8f2a1e5 100644
--- a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
+import android.view.Display;
import android.view.DisplayAddress;
import androidx.test.filters.SmallTest;
@@ -65,11 +66,13 @@
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ true,
/* isEnabled= */ true, mDisplayIdProducerMock,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ false,
/* isEnabled= */ false, mDisplayIdProducerMock,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
assertEquals(testLayout, configLayout);
}
@@ -81,11 +84,13 @@
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ true,
/* isEnabled= */ true, mDisplayIdProducerMock,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ false,
/* isEnabled= */ false, mDisplayIdProducerMock,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
assertEquals(testLayout, configLayout);
}
@@ -99,13 +104,15 @@
Layout.Display display1 = testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true,
/* isEnabled= */ true, mDisplayIdProducerMock,
- /* brightnessThrottlingMapId= */ "concurrent");
+ /* brightnessThrottlingMapId= */ "concurrent",
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
display1.setPosition(Layout.Display.POSITION_FRONT);
Layout.Display display2 = testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false,
/* isEnabled= */ true, mDisplayIdProducerMock,
- /* brightnessThrottlingMapId= */ "concurrent");
+ /* brightnessThrottlingMapId= */ "concurrent",
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
display2.setPosition(Layout.Display.POSITION_REAR);
assertEquals(testLayout, configLayout);
@@ -119,6 +126,26 @@
assertEquals(Layout.Display.POSITION_REAR, configLayout.getAt(1).getPosition());
}
+ @Test
+ public void testRefreshRateZoneId() {
+ Layout configLayout = mDeviceStateToLayoutMap.get(3);
+
+ Layout testLayout = new Layout();
+ Layout.Display display1 = testLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true,
+ /* isEnabled= */ true, mDisplayIdProducerMock,
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+ display1.setRefreshRateZoneId("test1");
+ testLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false,
+ /* isEnabled= */ true, mDisplayIdProducerMock,
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+
+ assertEquals(testLayout, configLayout);
+ }
+
////////////////////
// Helper Methods //
////////////////////
@@ -166,6 +193,17 @@
+ "<brightnessThrottlingMapId>concurrent</brightnessThrottlingMapId>\n"
+ "</display>\n"
+ "</layout>\n"
+
+ + "<layout>\n"
+ + "<state>3</state> \n"
+ + "<display enabled=\"true\" defaultDisplay=\"true\" "
+ + "refreshRateZoneId=\"test1\">\n"
+ + "<address>345</address>\n"
+ + "</display>\n"
+ + "<display enabled=\"true\">\n"
+ + "<address>678</address>\n"
+ + "</display>\n"
+ + "</layout>\n"
+ "</layouts>\n";
}
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 42bdbec..0e9c171 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -155,6 +155,13 @@
assertEquals(90, mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate());
assertEquals(85, mDisplayDeviceConfig.getDefaultPeakRefreshRate());
assertEquals(45, mDisplayDeviceConfig.getDefaultRefreshRate());
+
+ assertEquals(2, mDisplayDeviceConfig.getRefreshRangeProfiles().size());
+ assertEquals(60, mDisplayDeviceConfig.getRefreshRange("test1").min, SMALL_DELTA);
+ assertEquals(60, mDisplayDeviceConfig.getRefreshRange("test1").max, SMALL_DELTA);
+ assertEquals(80, mDisplayDeviceConfig.getRefreshRange("test2").min, SMALL_DELTA);
+ assertEquals(90, mDisplayDeviceConfig.getRefreshRange("test2").max, SMALL_DELTA);
+
assertArrayEquals(new int[]{45, 55},
mDisplayDeviceConfig.getLowDisplayBrightnessThresholds());
assertArrayEquals(new int[]{50, 60},
@@ -288,6 +295,9 @@
DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE);
+
+ assertEquals(0, mDisplayDeviceConfig.getRefreshRangeProfiles().size());
+
assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(),
LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(),
@@ -547,6 +557,20 @@
+ "<refreshRate>\n"
+ "<defaultRefreshRate>45</defaultRefreshRate>\n"
+ "<defaultPeakRefreshRate>85</defaultPeakRefreshRate>\n"
+ + "<refreshRateZoneProfiles>"
+ + "<refreshRateZoneProfile id=\"test1\">"
+ + "<refreshRateRange>\n"
+ + "<minimum>60</minimum>\n"
+ + "<maximum>60</maximum>\n"
+ + "</refreshRateRange>\n"
+ + "</refreshRateZoneProfile>\n"
+ + "<refreshRateZoneProfile id=\"test2\">"
+ + "<refreshRateRange>\n"
+ + "<minimum>80</minimum>\n"
+ + "<maximum>90</maximum>\n"
+ + "</refreshRateRange>\n"
+ + "</refreshRateZoneProfile>\n"
+ + "</refreshRateZoneProfiles>"
+ "<lowerBlockingZoneConfigs>\n"
+ "<defaultRefreshRate>75</defaultRefreshRate>\n"
+ "<blockingZoneThreshold>\n"
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 8a37ed9..bd2b5fd 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -299,9 +299,11 @@
Layout layout1 = new Layout();
layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
- /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
layout1.createDisplayLocked(info(device2).address, /* isDefault= */ false,
- /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
assertThat(layout1.size()).isEqualTo(2);
final int logicalId2 = layout1.getByAddress(info(device2).address).getLogicalDisplayId();
@@ -335,16 +337,19 @@
Layout layout1 = new Layout();
layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
- /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
final int layoutState2 = 2;
Layout layout2 = new Layout();
layout2.createDisplayLocked(info(device2).address, /* isDefault= */ false,
- /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
// Device3 is the default display.
layout2.createDisplayLocked(info(device3).address, /* isDefault= */ true,
- /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(layoutState2)).thenReturn(layout2);
assertThat(layout2.size()).isEqualTo(2);
final int logicalId2 = layout2.getByAddress(info(device2).address).getLogicalDisplayId();
@@ -567,17 +572,21 @@
Layout layout = new Layout();
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
true, true, mIdProducer,
- /* brightnessThrottlingMapId= */ "concurrent");
+ /* brightnessThrottlingMapId= */ "concurrent",
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
false, true, mIdProducer,
- /* brightnessThrottlingMapId= */ "concurrent");
+ /* brightnessThrottlingMapId= */ "concurrent",
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
layout = new Layout();
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
- false, false, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ false, false, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
- true, true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ true, true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout);
when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout);
@@ -604,6 +613,10 @@
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+ assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device1)
+ .getLeadDisplayLocked());
+ assertEquals(0, mLogicalDisplayMapper.getDisplayLocked(device2)
+ .getLeadDisplayLocked());
assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1)
.getBrightnessThrottlingDataIdLocked());
assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2)
@@ -655,19 +668,22 @@
/* isDefault= */ true,
/* isEnabled= */ true,
mIdProducer,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
threeDevicesEnabledLayout.createDisplayLocked(
displayAddressTwo,
/* isDefault= */ false,
/* isEnabled= */ true,
mIdProducer,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
threeDevicesEnabledLayout.createDisplayLocked(
displayAddressThree,
/* isDefault= */ false,
/* isEnabled= */ true,
mIdProducer,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT))
.thenReturn(threeDevicesEnabledLayout);
@@ -703,19 +719,22 @@
/* isDefault= */ true,
/* isEnabled= */ true,
mIdProducer,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
oneDeviceEnabledLayout.createDisplayLocked(
displayAddressTwo,
/* isDefault= */ false,
/* isEnabled= */ false,
mIdProducer,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
oneDeviceEnabledLayout.createDisplayLocked(
displayAddressThree,
/* isDefault= */ false,
/* isEnabled= */ false,
mIdProducer,
- /* brightnessThrottlingMapId= */ null);
+ /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout);
when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);
@@ -790,10 +809,11 @@
Layout layout = new Layout();
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
- true, true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ true, true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
false, true, mIdProducer, /* brightnessThrottlingMapId= */ null,
- POSITION_REAR);
+ POSITION_REAR, Display.DEFAULT_DISPLAY);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 5ca695b..c9612cd 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -49,8 +49,8 @@
import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
-import android.provider.Settings;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.security.KeyStore;
import androidx.test.InstrumentationRegistry;
@@ -83,16 +83,15 @@
protected static final int MANAGED_PROFILE_USER_ID = 12;
protected static final int TURNED_OFF_PROFILE_USER_ID = 17;
protected static final int SECONDARY_USER_ID = 20;
+ protected static final int TERTIARY_USER_ID = 21;
- private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null,
- UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY
- | UserInfo.FLAG_MAIN);
- private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null,
- UserInfo.FLAG_INITIALIZED);
+ protected UserInfo mPrimaryUserInfo;
+ protected UserInfo mSecondaryUserInfo;
+ protected UserInfo mTertiaryUserInfo;
private ArrayList<UserInfo> mPrimaryUserProfiles = new ArrayList<>();
- LockSettingsService mService;
+ LockSettingsServiceTestable mService;
LockSettingsInternal mLocalService;
MockLockSettingsContext mContext;
@@ -117,6 +116,7 @@
FingerprintManager mFingerprintManager;
FaceManager mFaceManager;
PackageManager mPackageManager;
+ LockSettingsServiceTestable.MockInjector mInjector;
@Rule
public FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
@@ -162,22 +162,61 @@
mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService,
mUserManager, mPasswordSlotManager);
mAuthSecretService = mock(IAuthSecret.class);
- mService = new LockSettingsServiceTestable(mContext, mStorage,
- mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
- mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager,
- mUserManagerInternal, mDeviceStateCache);
+ mInjector =
+ new LockSettingsServiceTestable.MockInjector(
+ mContext,
+ mStorage,
+ mKeyStore,
+ mActivityManager,
+ setUpStorageManagerMock(),
+ mSpManager,
+ mGsiService,
+ mRecoverableKeyStoreManager,
+ mUserManagerInternal,
+ mDeviceStateCache);
+ mService =
+ new LockSettingsServiceTestable(mInjector, mGateKeeperService, mAuthSecretService);
mService.mHasSecureLockScreen = true;
- when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
- mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
+ mPrimaryUserInfo =
+ new UserInfo(
+ PRIMARY_USER_ID,
+ null,
+ null,
+ UserInfo.FLAG_INITIALIZED
+ | UserInfo.FLAG_ADMIN
+ | UserInfo.FLAG_PRIMARY
+ | UserInfo.FLAG_MAIN
+ | UserInfo.FLAG_FULL);
+ mSecondaryUserInfo =
+ new UserInfo(
+ SECONDARY_USER_ID,
+ null,
+ null,
+ UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_FULL);
+ mTertiaryUserInfo =
+ new UserInfo(
+ TERTIARY_USER_ID,
+ null,
+ null,
+ UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_FULL);
+
+ when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserInfo);
+ when(mUserManagerInternal.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserInfo);
+ mPrimaryUserProfiles.add(mPrimaryUserInfo);
installChildProfile(MANAGED_PROFILE_USER_ID);
installAndTurnOffChildProfile(TURNED_OFF_PROFILE_USER_ID);
for (UserInfo profile : mPrimaryUserProfiles) {
when(mUserManager.getProfiles(eq(profile.id))).thenReturn(mPrimaryUserProfiles);
}
- when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO);
+ when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(mSecondaryUserInfo);
+ when(mUserManagerInternal.getUserInfo(eq(SECONDARY_USER_ID)))
+ .thenReturn(mSecondaryUserInfo);
+ when(mUserManager.getUserInfo(eq(TERTIARY_USER_ID))).thenReturn(mTertiaryUserInfo);
+ when(mUserManagerInternal.getUserInfo(eq(TERTIARY_USER_ID))).thenReturn(mTertiaryUserInfo);
final ArrayList<UserInfo> allUsers = new ArrayList<>(mPrimaryUserProfiles);
- allUsers.add(SECONDARY_USER_INFO);
+ allUsers.add(mSecondaryUserInfo);
+ allUsers.add(mTertiaryUserInfo);
when(mUserManager.getUsers()).thenReturn(allUsers);
when(mActivityManager.unlockUser2(anyInt(), any())).thenAnswer(
@@ -227,9 +266,10 @@
userInfo.profileGroupId = PRIMARY_USER_ID;
mPrimaryUserProfiles.add(userInfo);
when(mUserManager.getUserInfo(eq(profileId))).thenReturn(userInfo);
- when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO);
+ when(mUserManager.getProfileParent(eq(profileId))).thenReturn(mPrimaryUserInfo);
when(mUserManager.isUserRunning(eq(profileId))).thenReturn(true);
when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(true);
+ when(mUserManagerInternal.getUserInfo(eq(profileId))).thenReturn(userInfo);
// TODO(b/258213147): Remove
when(mUserManagerInternal.isUserManaged(eq(profileId))).thenReturn(true);
when(mDeviceStateCache.isUserOrganizationManaged(eq(profileId)))
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index f0f0632..9686c38 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -30,6 +30,7 @@
import android.os.storage.IStorageManager;
import android.security.KeyStore;
import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.service.gatekeeper.IGateKeeperService;
import com.android.internal.widget.LockscreenCredential;
import com.android.server.ServiceThread;
@@ -40,7 +41,7 @@
public class LockSettingsServiceTestable extends LockSettingsService {
- private static class MockInjector extends LockSettingsService.Injector {
+ public static class MockInjector extends LockSettingsService.Injector {
private LockSettingsStorage mLockSettingsStorage;
private KeyStore mKeyStore;
@@ -52,6 +53,9 @@
private UserManagerInternal mUserManagerInternal;
private DeviceStateCache mDeviceStateCache;
+ public boolean mIsHeadlessSystemUserMode = false;
+ public boolean mIsMainUserPermanentAdmin = false;
+
public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
IActivityManager activityManager,
IStorageManager storageManager, SyntheticPasswordManager spManager,
@@ -140,19 +144,22 @@
return mock(ManagedProfilePasswordCache.class);
}
+ @Override
+ public boolean isHeadlessSystemUserMode() {
+ return mIsHeadlessSystemUserMode;
+ }
+
+ @Override
+ public boolean isMainUserPermanentAdmin() {
+ return mIsMainUserPermanentAdmin;
+ }
}
- public MockInjector mInjector;
-
- protected LockSettingsServiceTestable(Context context,
- LockSettingsStorage storage, FakeGateKeeperService gatekeeper, KeyStore keystore,
- IStorageManager storageManager, IActivityManager mActivityManager,
- SyntheticPasswordManager spManager, IAuthSecret authSecretService,
- FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
- UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
- super(new MockInjector(context, storage, keystore, mActivityManager,
- storageManager, spManager, gsiService, recoverableKeyStoreManager,
- userManagerInternal, deviceStateCache));
+ protected LockSettingsServiceTestable(
+ LockSettingsService.Injector injector,
+ IGateKeeperService gatekeeper,
+ IAuthSecret authSecretService) {
+ super(injector);
mGateKeeperService = gatekeeper;
mAuthSecretService = authSecretService;
}
@@ -199,4 +206,10 @@
UserInfo userInfo = mUserManager.getUserInfo(userId);
return userInfo.isCloneProfile() || userInfo.isManagedProfile();
}
+
+ void clearAuthSecret() {
+ synchronized (mHeadlessAuthSecretLock) {
+ mAuthSecret = null;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 57593cf..62d8a76 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -16,6 +16,10 @@
package com.android.server.locksettings;
+import static android.content.pm.UserInfo.FLAG_FULL;
+import static android.content.pm.UserInfo.FLAG_MAIN;
+import static android.content.pm.UserInfo.FLAG_PRIMARY;
+
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
@@ -27,9 +31,9 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -247,6 +251,15 @@
@Test
public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecret() throws RemoteException {
+ initSpAndSetCredential(PRIMARY_USER_ID, newPassword(null));
+ reset(mAuthSecretService);
+ mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+ verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
+ }
+
+ @Test
+ public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecretIfPasswordIsCleared()
+ throws RemoteException {
LockscreenCredential password = newPassword("password");
initSpAndSetCredential(PRIMARY_USER_ID, password);
mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
@@ -256,6 +269,56 @@
verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
}
+ private void setupHeadlessTest() {
+ mInjector.mIsHeadlessSystemUserMode = true;
+ mInjector.mIsMainUserPermanentAdmin = true;
+ mPrimaryUserInfo.flags &= ~(FLAG_FULL | FLAG_PRIMARY);
+ mSecondaryUserInfo.flags |= FLAG_MAIN;
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+ mService.initializeSyntheticPassword(SECONDARY_USER_ID);
+ mService.initializeSyntheticPassword(TERTIARY_USER_ID);
+ reset(mAuthSecretService);
+ }
+
+ @Test
+ public void testHeadlessSystemUserDoesNotPassAuthSecret() throws RemoteException {
+ setupHeadlessTest();
+ mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+ verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
+ }
+
+ @Test
+ public void testHeadlessSecondaryUserPassesAuthSecret() throws RemoteException {
+ setupHeadlessTest();
+ mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+ verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
+ }
+
+ @Test
+ public void testHeadlessTertiaryUserPassesSameAuthSecret() throws RemoteException {
+ setupHeadlessTest();
+ mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+ var captor = ArgumentCaptor.forClass(byte[].class);
+ verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
+ var value = captor.getValue();
+ reset(mAuthSecretService);
+ mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
+ verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
+ }
+
+ @Test
+ public void testHeadlessTertiaryUserPassesSameAuthSecretAfterReset() throws RemoteException {
+ setupHeadlessTest();
+ mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+ var captor = ArgumentCaptor.forClass(byte[].class);
+ verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
+ var value = captor.getValue();
+ mService.clearAuthSecret();
+ reset(mAuthSecretService);
+ mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
+ verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
+ }
+
@Test
public void testTokenBasedResetPassword() throws RemoteException {
LockscreenCredential password = newPassword("password");
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
index 966c047..50f3a88 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
@@ -8,6 +8,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+
import com.google.android.collect.Sets;
import org.junit.Before;
@@ -29,7 +30,7 @@
// sequentially, starting at slot 0.
@Test
public void testFrpWeaverSlotNotReused() {
- final int userId = 10;
+ final int userId = SECONDARY_USER_ID;
final int frpWeaverSlot = 0;
setDeviceProvisioned(false);
@@ -45,7 +46,7 @@
// it's here as a control for testFrpWeaverSlotNotReused().
@Test
public void testFrpWeaverSlotReused() {
- final int userId = 10;
+ final int userId = SECONDARY_USER_ID;
final int frpWeaverSlot = 0;
setDeviceProvisioned(true);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index c760efd..8a292cb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -302,7 +302,7 @@
@MediumTest
@Test
public void testRemoveUserByHandle_ThrowsException() {
- assertThrows(IllegalArgumentException.class, () -> removeUser(null));
+ assertThrows(IllegalArgumentException.class, () -> mUserManager.removeUser(null));
}
@MediumTest
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 23587c9..5c69c84 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -78,6 +78,7 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
@@ -10083,4 +10084,211 @@
mInternalService.sendReviewPermissionsNotification();
verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
}
+
+ @Test
+ public void fixSystemNotification_withOnGoingFlag_shouldBeNonDismissible()
+ throws Exception {
+ // Given: a notification from an app on the system partition has the flag
+ // FLAG_ONGOING_EVENT set
+ // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
+ mService.setOngoingDismissal(true);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setOngoing(true)
+ .build();
+
+ final ApplicationInfo systemAppInfo = new ApplicationInfo();
+ systemAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+ when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(systemAppInfo);
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, PKG, "tag", 9, 0);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should be set
+ assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
+
+ // Avoid affecting other tests
+ mService.setOngoingDismissal(false);
+ }
+
+ @Test
+ public void fixMediaNotification_withOnGoingFlag_shouldBeNonDismissible()
+ throws Exception {
+ // Given: a media notification has the flag FLAG_ONGOING_EVENT set
+ // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
+ mService.setOngoingDismissal(true);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setOngoing(true)
+ .setStyle(new Notification.MediaStyle()
+ .setMediaSession(mock(MediaSession.Token.class)))
+ .build();
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, PKG, "tag", 9, 0);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should be set
+ assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
+
+ // Avoid affecting other tests
+ mService.setOngoingDismissal(false);
+ }
+
+ @Test
+ public void fixNonExemptNotification_withOnGoingFlag_shouldBeDismissible() throws Exception {
+ // Given: a non-exempt notification has the flag FLAG_ONGOING_EVENT set
+ // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
+ mService.setOngoingDismissal(true);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setOngoing(true)
+ .build();
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, PKG, "tag", 9, 0);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should not be set
+ assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
+
+ // Avoid affecting other tests
+ mService.setOngoingDismissal(false);
+ }
+
+ @Test
+ public void fixNonExemptNotification_withNoDismissFlag_shouldBeDismissible()
+ throws Exception {
+ // Given: a non-exempt notification has the flag FLAG_NO_DISMISS set (even though this is
+ // not allowed)
+ // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
+ mService.setOngoingDismissal(true);
+ Notification n = new Notification.Builder(mContext, "test")
+ .build();
+ n.flags |= Notification.FLAG_NO_DISMISS;
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, PKG, "tag", 9, 0);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should be cleared
+ assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
+
+ // Avoid affecting other tests
+ mService.setOngoingDismissal(false);
+ }
+
+ @Test
+ public void fixSystemNotification_withoutOnGoingFlag_shouldBeDismissible() throws Exception {
+ // Given: a notification from an app on the system partition doesn't have the flag
+ // FLAG_ONGOING_EVENT set
+ // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
+ mService.setOngoingDismissal(true);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setOngoing(false)
+ .build();
+
+ final ApplicationInfo systemAppInfo = new ApplicationInfo();
+ systemAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+ when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(systemAppInfo);
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, PKG, "tag", 9, 0);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should not be set
+ assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
+
+ // Avoid affecting other tests
+ mService.setOngoingDismissal(false);
+ }
+
+ @Test
+ public void fixSystemNotification_withoutOnGoingFlag_withNoDismissFlag_shouldBeDismissible()
+ throws Exception {
+ // Given: a notification from an app on the system partition doesn't have the flag
+ // FLAG_ONGOING_EVENT set, but has the flag FLAG_NO_DISMISS set
+ // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
+ mService.setOngoingDismissal(true);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setOngoing(false)
+ .build();
+ n.flags |= Notification.FLAG_NO_DISMISS;
+
+ final ApplicationInfo systemAppInfo = new ApplicationInfo();
+ systemAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+ when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(systemAppInfo);
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, PKG, "tag", 9, 0);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should be cleared
+ assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
+
+ // Avoid affecting other tests
+ mService.setOngoingDismissal(false);
+ }
+
+ @Test
+ public void fixMediaNotification_withoutOnGoingFlag_shouldBeDismissible() throws Exception {
+ // Given: a media notification doesn't have the flag FLAG_ONGOING_EVENT set
+ // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
+ mService.setOngoingDismissal(true);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setOngoing(false)
+ .setStyle(new Notification.MediaStyle()
+ .setMediaSession(mock(MediaSession.Token.class)))
+ .build();
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, PKG, "tag", 9, 0);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should not be set
+ assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
+
+ // Avoid affecting other tests
+ mService.setOngoingDismissal(false);
+ }
+
+ @Test
+ public void fixMediaNotification_withoutOnGoingFlag_withNoDismissFlag_shouldBeDismissible()
+ throws Exception {
+ // Given: a media notification doesn't have the flag FLAG_ONGOING_EVENT set,
+ // but has the flag FLAG_NO_DISMISS set
+ // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
+ mService.setOngoingDismissal(true);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setOngoing(false)
+ .setStyle(new Notification.MediaStyle()
+ .setMediaSession(mock(MediaSession.Token.class)))
+ .build();
+ n.flags |= Notification.FLAG_NO_DISMISS;
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, PKG, "tag", 9, 0);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should be cleared
+ assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
+
+ // Avoid affecting other tests
+ mService.setOngoingDismissal(false);
+ }
+
+ @Test
+ public void fixNonExempt_Notification_withoutOnGoingFlag_shouldBeDismissible()
+ throws Exception {
+ // Given: a non-exempt notification has the flag FLAG_ONGOING_EVENT set
+ // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
+ mService.setOngoingDismissal(true);
+ mService.setOngoingDismissal(true);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setOngoing(false)
+ .build();
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, PKG, "tag", 9, 0);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should not be set
+ assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
+
+ // Avoid affecting other tests
+ mService.setOngoingDismissal(false);
+ }
+
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 61a6985..b8c94da 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -36,6 +36,8 @@
int countLogSmartSuggestionsVisible = 0;
Set<Integer> mChannelToastsSent = new HashSet<>();
+ public boolean ONGOING_DISMISSAL = false;
+
String stringArrayResourceValue;
@Nullable
NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback;
@@ -159,4 +161,9 @@
return mGetStrongAuthForUserReturnValue;
}
}
+
+ // Mock SystemProperties
+ protected void setOngoingDismissal(boolean ongoingDismissal) {
+ ONGOING_DISMISSAL = ongoingDismissal;
+ }
}
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 abc0c14..1ce8c61 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -105,6 +105,7 @@
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.app.ActivityTaskManager;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
@@ -123,6 +124,7 @@
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.view.ContentRecordingSession;
+import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
@@ -1831,6 +1833,111 @@
}
@Test
+ public void testSecondaryInternalDisplayRotationFollowsDefaultDisplay() {
+ // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
+ doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
+
+ final DisplayRotationCoordinator coordinator =
+ mRootWindowContainer.getDisplayRotationCoordinator();
+ final DisplayContent defaultDisplayContent = mDisplayContent;
+ final DisplayRotation defaultDisplayRotation = defaultDisplayContent.getDisplayRotation();
+ coordinator.removeDefaultDisplayRotationChangedCallback();
+
+ DeviceStateController deviceStateController = mock(DeviceStateController.class);
+ when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
+ .thenReturn(true);
+
+ // Create secondary display
+ final DisplayContent secondaryDisplayContent =
+ createSecondaryDisplayContent(Display.TYPE_INTERNAL, deviceStateController);
+ final DisplayRotation secondaryDisplayRotation =
+ secondaryDisplayContent.getDisplayRotation();
+ try {
+ // TestDisplayContent bypasses this method but we need it for this test
+ doCallRealMethod().when(secondaryDisplayRotation).updateRotationUnchecked(anyBoolean());
+
+ // TestDisplayContent creates this as a mock. Lets set it up to test our use case.
+ when(secondaryDisplayContent.mDeviceStateController
+ .shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()).thenReturn(
+ true);
+
+ // Check that secondary display registered callback
+ assertEquals(secondaryDisplayRotation.mDefaultDisplayRotationChangedCallback,
+ coordinator.mDefaultDisplayRotationChangedCallback);
+
+ // Set the default display to a known orientation. This may be a zero or non-zero
+ // rotation since mDisplayInfo.logicalWidth/Height depends on the DUT's default display
+ defaultDisplayRotation.updateOrientation(SCREEN_ORIENTATION_PORTRAIT, false);
+ assertEquals(defaultDisplayRotation.mPortraitRotation,
+ defaultDisplayRotation.getRotation());
+ assertEquals(defaultDisplayRotation.mPortraitRotation,
+ coordinator.getDefaultDisplayCurrentRotation());
+
+ // Check that in the initial state, the secondary display is in the right rotation
+ assertRotationsAreCorrectlyReversed(defaultDisplayRotation.getRotation(),
+ secondaryDisplayRotation.getRotation());
+
+ // Update primary display rotation, check display coordinator rotation is the default
+ // display's landscape rotation, and that the secondary display rotation is correct.
+ defaultDisplayRotation.updateOrientation(SCREEN_ORIENTATION_LANDSCAPE, false);
+ assertEquals(defaultDisplayRotation.mLandscapeRotation,
+ defaultDisplayRotation.getRotation());
+ assertEquals(defaultDisplayRotation.mLandscapeRotation,
+ coordinator.getDefaultDisplayCurrentRotation());
+ assertRotationsAreCorrectlyReversed(defaultDisplayRotation.getRotation(),
+ secondaryDisplayRotation.getRotation());
+ } finally {
+ secondaryDisplayRotation.removeDefaultDisplayRotationChangedCallback();
+ }
+ }
+
+ @Test
+ public void testSecondaryNonInternalDisplayDoesNotFollowDefaultDisplay() {
+ // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
+ doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
+
+ final DisplayRotationCoordinator coordinator =
+ mRootWindowContainer.getDisplayRotationCoordinator();
+ coordinator.removeDefaultDisplayRotationChangedCallback();
+
+ DeviceStateController deviceStateController = mock(DeviceStateController.class);
+ when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
+ .thenReturn(true);
+
+ // Create secondary non-internal displays
+ createSecondaryDisplayContent(Display.TYPE_EXTERNAL, deviceStateController);
+ assertNull(coordinator.mDefaultDisplayRotationChangedCallback);
+ createSecondaryDisplayContent(Display.TYPE_VIRTUAL, deviceStateController);
+ assertNull(coordinator.mDefaultDisplayRotationChangedCallback);
+ }
+
+ private DisplayContent createSecondaryDisplayContent(int displayType,
+ @NonNull DeviceStateController deviceStateController) {
+ final DisplayInfo secondaryDisplayInfo = new DisplayInfo();
+ secondaryDisplayInfo.copyFrom(mDisplayInfo);
+ secondaryDisplayInfo.type = displayType;
+
+ return new TestDisplayContent.Builder(mAtm, secondaryDisplayInfo)
+ .setDeviceStateController(deviceStateController)
+ .build();
+ }
+
+ private static void assertRotationsAreCorrectlyReversed(@Surface.Rotation int rotation1,
+ @Surface.Rotation int rotation2) {
+ if (rotation1 == ROTATION_0) {
+ assertEquals(rotation1, rotation2);
+ } else if (rotation1 == ROTATION_180) {
+ assertEquals(rotation1, rotation2);
+ } else if (rotation1 == ROTATION_90) {
+ assertEquals(ROTATION_270, rotation2);
+ } else if (rotation1 == ROTATION_270) {
+ assertEquals(ROTATION_90, rotation2);
+ } else {
+ throw new IllegalArgumentException("Unknown rotation: " + rotation1 + ", " + rotation2);
+ }
+ }
+
+ @Test
public void testRemoteRotation() {
final DisplayContent dc = mDisplayContent;
final DisplayRotation dr = dc.getDisplayRotation();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
new file mode 100644
index 0000000..4557df0
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
@@ -0,0 +1,95 @@
+/*
+ * 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;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+
+import android.annotation.NonNull;
+import android.platform.test.annotations.Presubmit;
+import android.view.Surface;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Test class for {@link DisplayRotationCoordinator}
+ *
+ * Build/Install/Run:
+ * atest DisplayRotationCoordinatorTests
+ */
+@SmallTest
+@Presubmit
+public class DisplayRotationCoordinatorTests {
+
+ @NonNull
+ private final DisplayRotationCoordinator mCoordinator = new DisplayRotationCoordinator();
+
+ @Test
+ public void testDefaultDisplayRotationChangedWhenNoCallbackRegistered() {
+ // Does not cause NPE
+ mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
+ }
+
+ @Test (expected = UnsupportedOperationException.class)
+ public void testSecondRegistrationWithoutRemovingFirst() {
+ Runnable callback1 = mock(Runnable.class);
+ Runnable callback2 = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
+ assertEquals(callback1, mCoordinator.mDefaultDisplayRotationChangedCallback);
+ }
+
+ @Test
+ public void testSecondRegistrationAfterRemovingFirst() {
+ Runnable callback1 = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
+ mCoordinator.removeDefaultDisplayRotationChangedCallback();
+
+ Runnable callback2 = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
+
+ mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
+ verify(callback2).run();
+ verify(callback1, never()).run();
+ }
+
+ @Test
+ public void testRegisterThenDefaultDisplayRotationChanged() {
+ Runnable callback = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
+ assertEquals(Surface.ROTATION_0, mCoordinator.getDefaultDisplayCurrentRotation());
+ verify(callback, never()).run();
+
+ mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
+ verify(callback).run();
+ assertEquals(Surface.ROTATION_90, mCoordinator.getDefaultDisplayCurrentRotation());
+ }
+
+ @Test
+ public void testDefaultDisplayRotationChangedThenRegister() {
+ mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
+ Runnable callback = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
+ verify(callback).run();
+ assertEquals(Surface.ROTATION_90, mCoordinator.getDefaultDisplayCurrentRotation());
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index ed2b0a3..21e8ec4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -1135,7 +1135,7 @@
mDeviceStateController = mock(DeviceStateController.class);
mTarget = new DisplayRotation(sMockWm, mMockDisplayContent, mMockDisplayAddress,
mMockDisplayPolicy, mMockDisplayWindowSettings, mMockContext, new Object(),
- mDeviceStateController) {
+ mDeviceStateController, mock(DisplayRotationCoordinator.class)) {
@Override
DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy(
WindowManagerService service, DisplayContent displayContent) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index 4eaae9f..d1a41ae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -552,7 +552,7 @@
/** Please use the {@link Builder} to create. */
DualDisplayContent(RootWindowContainer rootWindowContainer,
Display display) {
- super(rootWindowContainer, display);
+ super(rootWindowContainer, display, mock(DeviceStateController.class));
mFirstRoot = getGroupRoot(FEATURE_FIRST_ROOT);
mSecondRoot = getGroupRoot(FEATURE_SECOND_ROOT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index bcaf886..0037e57 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -24,7 +24,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.hardware.display.DisplayManager;
@@ -289,6 +291,14 @@
assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+
+ // If there will be display size change when switching from preferred mode to default mode,
+ // then keep the current preferred mode during animating.
+ mDisplayInfo = spy(mDisplayInfo);
+ final Mode defaultMode = new Mode(4321 /* width */, 1234 /* height */, LOW_REFRESH_RATE);
+ doReturn(defaultMode).when(mDisplayInfo).getDefaultMode();
+ mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist);
+ assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 7ad5442..6db0f27 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1895,6 +1895,43 @@
}
@Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION})
+ public void testOverrideRespectRequestedOrientationIsEnabled_orientationIsRespected() {
+ // Set up a display in landscape
+ setUpDisplaySizeWithApp(2800, 1400);
+
+ final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
+ RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
+ activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ // Display should be rotated.
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, activity.mDisplayContent.getOrientation());
+
+ // No size compat mode
+ assertFalse(activity.inSizeCompatMode());
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION})
+ public void testOverrideRespectRequestedOrientationIsEnabled_multiWindow_orientationIgnored() {
+ // Set up a display in landscape
+ setUpDisplaySizeWithApp(2800, 1400);
+
+ final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
+ RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
+ TaskFragment taskFragment = activity.getTaskFragment();
+ spyOn(taskFragment);
+ activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ doReturn(WINDOWING_MODE_MULTI_WINDOW).when(taskFragment).getWindowingMode();
+
+ // Display should not be rotated.
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.mDisplayContent.getOrientation());
+
+ // No size compat mode
+ assertFalse(activity.inSizeCompatMode());
+ }
+
+ @Test
public void testSplitAspectRatioForUnresizableLandscapeApps() {
// Set up a display in portrait and ignoring orientation request.
int screenWidth = 1400;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 5099869..dab842c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -45,11 +45,13 @@
import static org.mockito.Mockito.clearInvocations;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl;
import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer;
@@ -299,35 +301,44 @@
@Test
public void testEmbeddedTaskFragmentEnterPip_singleActivity_resetOrganizerOverrideConfig() {
- final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
- .setOrganizer(mOrganizer)
- .setFragmentToken(new Binder())
- .setCreateParentTask()
- .createActivityCount(1)
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment taskFragment0 = createTaskFragmentWithEmbeddedActivity(task, mOrganizer);
+ final TaskFragment taskFragment1 = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
.build();
- final Task task = taskFragment.getTask();
- final ActivityRecord activity = taskFragment.getTopMostActivity();
+ final ActivityRecord activity = taskFragment0.getTopMostActivity();
final Rect taskFragmentBounds = new Rect(0, 0, 300, 1000);
task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- taskFragment.setBounds(taskFragmentBounds);
+ taskFragment0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ taskFragment0.setBounds(taskFragmentBounds);
+ taskFragment0.setAdjacentTaskFragment(taskFragment1);
+ taskFragment0.setCompanionTaskFragment(taskFragment1);
+ taskFragment0.setAnimationParams(new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(Color.GREEN)
+ .build());
assertEquals(taskFragmentBounds, activity.getBounds());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());
+ assertEquals(taskFragment1, taskFragment0.getAdjacentTaskFragment());
+ assertEquals(taskFragment1, taskFragment0.getCompanionTaskFragment());
+ assertNotEquals(TaskFragmentAnimationParams.DEFAULT, taskFragment0.getAnimationParams());
// Move activity to pinned root task.
mRootWindowContainer.moveActivityToPinnedRootTask(activity,
null /* launchIntoPipHostActivity */, "test");
// Ensure taskFragment requested config is reset.
- assertEquals(taskFragment, activity.getOrganizedTaskFragment());
+ assertEquals(taskFragment0, activity.getOrganizedTaskFragment());
assertEquals(task, activity.getTask());
assertTrue(task.inPinnedWindowingMode());
- assertTrue(taskFragment.inPinnedWindowingMode());
+ assertTrue(taskFragment0.inPinnedWindowingMode());
final Rect taskBounds = task.getBounds();
- assertEquals(taskBounds, taskFragment.getBounds());
+ assertEquals(taskBounds, taskFragment0.getBounds());
assertEquals(taskBounds, activity.getBounds());
- assertEquals(Configuration.EMPTY, taskFragment.getRequestedOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, taskFragment0.getRequestedOverrideConfiguration());
+ assertNull(taskFragment0.getAdjacentTaskFragment());
+ assertNull(taskFragment0.getCompanionTaskFragment());
+ assertEquals(TaskFragmentAnimationParams.DEFAULT, taskFragment0.getAnimationParams());
// Because the whole Task is entering PiP, no need to record for future reparent.
assertNull(activity.mLastTaskFragmentOrganizerBeforePip);
}
@@ -335,18 +346,8 @@
@Test
public void testEmbeddedTaskFragmentEnterPip_multiActivities_notifyOrganizer() {
final Task task = createTask(mDisplayContent);
- final TaskFragment taskFragment0 = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .setOrganizer(mOrganizer)
- .setFragmentToken(new Binder())
- .createActivityCount(1)
- .build();
- final TaskFragment taskFragment1 = new TaskFragmentBuilder(mAtm)
- .setParentTask(task)
- .setOrganizer(mOrganizer)
- .setFragmentToken(new Binder())
- .createActivityCount(1)
- .build();
+ final TaskFragment taskFragment0 = createTaskFragmentWithEmbeddedActivity(task, mOrganizer);
+ final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, mOrganizer);
final ActivityRecord activity0 = taskFragment0.getTopMostActivity();
final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
activity0.setVisibility(true /* visible */, false /* deferHidingClient */);
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 83be4f0..fec079b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -27,8 +27,10 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
@@ -51,8 +53,9 @@
public static final int DEFAULT_LOGICAL_DISPLAY_DENSITY = 300;
/** Please use the {@link Builder} to create, visible for use in test builder overrides only. */
- TestDisplayContent(RootWindowContainer rootWindowContainer, Display display) {
- super(display, rootWindowContainer);
+ TestDisplayContent(RootWindowContainer rootWindowContainer, Display display,
+ @NonNull DeviceStateController deviceStateController) {
+ super(display, rootWindowContainer, deviceStateController);
// Normally this comes from display-properties as exposed by WM. Without that, just
// hard-code to FULLSCREEN for tests.
setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -97,6 +100,8 @@
private int mStatusBarHeight = 0;
private SettingsEntry mOverrideSettings;
private DisplayMetrics mDisplayMetrics;
+ @NonNull
+ private DeviceStateController mDeviceStateController = mock(DeviceStateController.class);
@Mock
Context mMockContext;
@Mock
@@ -198,8 +203,13 @@
com.android.internal.R.dimen.default_minimal_size_resizable_task);
return this;
}
+ Builder setDeviceStateController(@NonNull DeviceStateController deviceStateController) {
+ mDeviceStateController = deviceStateController;
+ return this;
+ }
TestDisplayContent createInternal(Display display) {
- return new TestDisplayContent(mService.mRootWindowContainer, display);
+ return new TestDisplayContent(mService.mRootWindowContainer, display,
+ mDeviceStateController);
}
TestDisplayContent build() {
SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock);
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 e5efe05..3e8d259 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -79,6 +79,7 @@
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.hardware.display.DisplayManager;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@@ -750,11 +751,11 @@
}
/**
- * Creates a {@link TaskFragment} with {@link ActivityRecord} and attach it to the
+ * Creates a {@link TaskFragment} with {@link ActivityRecord}, and attaches it to the
* {@code parentTask}.
*
- * @param parentTask the {@link Task} this TaskFragment is going to be attached
- * @return the created TaskFragment
+ * @param parentTask the {@link Task} this {@link TaskFragment} is going to be attached.
+ * @return the created {@link TaskFragment}
*/
static TaskFragment createTaskFragmentWithActivity(@NonNull Task parentTask) {
return new TaskFragmentBuilder(parentTask.mAtmService)
@@ -763,13 +764,27 @@
.build();
}
+ /**
+ * Creates an embedded {@link TaskFragment} organized by {@code organizer} with
+ * {@link ActivityRecord}, and attaches it to the {@code parentTask}.
+ *
+ * @param parentTask the {@link Task} this {@link TaskFragment} is going to be attached.
+ * @param organizer the {@link TaskFragmentOrganizer} this {@link TaskFragment} is going to be
+ * organized by.
+ * @return the created {@link TaskFragment}
+ */
static TaskFragment createTaskFragmentWithEmbeddedActivity(@NonNull Task parentTask,
- TaskFragmentOrganizer organizer) {
- return new TaskFragmentBuilder(parentTask.mAtmService)
+ @NonNull TaskFragmentOrganizer organizer) {
+ final IBinder fragmentToken = new Binder();
+ final TaskFragment taskFragment = new TaskFragmentBuilder(parentTask.mAtmService)
.setParentTask(parentTask)
.createActivityCount(1)
.setOrganizer(organizer)
+ .setFragmentToken(fragmentToken)
.build();
+ parentTask.mAtmService.mWindowOrganizerController.mLaunchTaskFragments
+ .put(fragmentToken, taskFragment);
+ return taskFragment;
}
/** Creates a {@link DisplayContent} that supports IME and adds it to the system. */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 64ed453..73b5afe 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -184,12 +184,12 @@
}
open fun cujCompleted() {
- entireScreenCovered()
- statusBarLayerIsVisibleAtStartAndEnd()
- statusBarLayerPositionAtStartAndEnd()
- statusBarWindowIsAlwaysVisible()
- visibleLayersShownMoreThanOneConsecutiveEntry()
- visibleWindowsShownMoreThanOneConsecutiveEntry()
+ runAndIgnoreAssumptionViolation { entireScreenCovered() }
+ runAndIgnoreAssumptionViolation { statusBarLayerIsVisibleAtStartAndEnd() }
+ runAndIgnoreAssumptionViolation { statusBarLayerPositionAtStartAndEnd() }
+ runAndIgnoreAssumptionViolation { statusBarWindowIsAlwaysVisible() }
+ runAndIgnoreAssumptionViolation { visibleLayersShownMoreThanOneConsecutiveEntry() }
+ runAndIgnoreAssumptionViolation { visibleWindowsShownMoreThanOneConsecutiveEntry() }
runAndIgnoreAssumptionViolation { taskBarLayerIsVisibleAtStartAndEnd() }
runAndIgnoreAssumptionViolation { taskBarWindowIsAlwaysVisible() }
runAndIgnoreAssumptionViolation { navBarLayerIsVisibleAtStartAndEnd() }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt
similarity index 63%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt
index ea67729..7f2e057f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt
@@ -33,13 +33,13 @@
* Test opening an activity that will launch another activity as ActivityEmbedding placeholder in
* split.
*
- * To run this test: `atest FlickerTests:OpenActivityEmbeddingPlaceholderSplit`
+ * To run this test: `atest FlickerTests:OpenActivityEmbeddingPlaceholderSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenActivityEmbeddingPlaceholderSplit(flicker: FlickerTest) :
+class OpenActivityEmbeddingPlaceholderSplitTest(flicker: FlickerTest) :
ActivityEmbeddingTestBase(flicker) {
/** {@inheritDoc} */
@@ -55,9 +55,21 @@
}
}
+ /** Main activity should become invisible after launching the placeholder primary activity. */
@Presubmit
@Test
- fun mainActivityBecomesInvisible() {
+ fun mainActivityWindowBecomesInvisible() {
+ flicker.assertWm {
+ isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .then()
+ .isAppWindowInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** Main activity should become invisible after launching the placeholder primary activity. */
+ @Presubmit
+ @Test
+ fun mainActivityLayerBecomesInvisible() {
flicker.assertLayers {
isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
.then()
@@ -65,76 +77,41 @@
}
}
+ /**
+ * Placeholder primary and secondary should become visible after launch. The windows are not
+ * necessarily to become visible at the same time.
+ */
@Presubmit
@Test
- fun placeholderSplitBecomesVisible() {
- flicker.assertLayers {
- isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+ fun placeholderSplitWindowsBecomeVisible() {
+ flicker.assertWm {
+ notContains(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
.then()
- .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+ .isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+ .then()
+ .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
}
- flicker.assertLayers {
- isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+ flicker.assertWm {
+ notContains(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
.then()
- .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+ .isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+ .then()
+ .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
}
}
- /** {@inheritDoc} */
- @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
-
- /** {@inheritDoc} */
+ /** Placeholder primary and secondary should become visible together after launch. */
@Presubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun statusBarLayerIsVisibleAtStartAndEnd() =
- super.statusBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ fun placeholderSplitLayersBecomeVisible() {
+ flicker.assertLayers {
+ isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+ isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+ .then()
+ .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+ .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+ }
+ }
companion object {
/**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt
new file mode 100644
index 0000000..5fff15c
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt
@@ -0,0 +1,124 @@
+/*
+ * 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
+
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test opening a secondary activity that will split with the main activity.
+ *
+ * To run this test: `atest FlickerTests:OpenActivityEmbeddingSecondaryToSplitTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenActivityEmbeddingSecondaryToSplitTest(flicker: FlickerTest) :
+ ActivityEmbeddingTestBase(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit = {
+ setup {
+ tapl.setExpectedRotationCheckEnabled(false)
+ testApp.launchViaIntent(wmHelper)
+ }
+ transitions { testApp.launchSecondaryActivity(wmHelper) }
+ teardown {
+ tapl.goHome()
+ testApp.exit(wmHelper)
+ }
+ }
+
+ /** Main activity should remain visible when enter split from fullscreen. */
+ @Presubmit
+ @Test
+ fun mainActivityWindowIsAlwaysVisible() {
+ flicker.assertWm {
+ isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /**
+ * Main activity surface is animated from fullscreen to ActivityEmbedding split.
+ * During the transition, there is a period of time that it is covered by a snapshot of itself.
+ */
+ @Presubmit
+ @Test
+ fun mainActivityLayerIsAlwaysVisible() {
+ flicker.assertLayers {
+ isVisible(
+ ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT.or(
+ ComponentNameMatcher.TRANSITION_SNAPSHOT
+ )
+ )
+ }
+ flicker.assertLayersEnd {
+ isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .isInvisible(ComponentNameMatcher.TRANSITION_SNAPSHOT)
+ }
+ }
+
+ /** Secondary activity should become visible after launching into split. */
+ @Presubmit
+ @Test
+ fun secondaryActivityWindowBecomesVisible() {
+ flicker.assertWm {
+ notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** Secondary activity should become visible after launching into split. */
+ @Presubmit
+ @Test
+ fun secondaryActivityLayerBecomesVisible() {
+ flicker.assertLayers {
+ isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
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 368cc56..a2db587 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
@@ -39,6 +39,25 @@
) : StandardAppHelper(instr, launcherName, component) {
/**
+ * Clicks the button to launch the secondary activity, which should split with the main activity
+ * based on the split pair rule.
+ */
+ fun launchSecondaryActivity(wmHelper: WindowManagerStateHelper) {
+ val launchButton =
+ uiDevice.wait(
+ Until.findObject(By.res(getPackage(), "launch_secondary_activity_button")),
+ FIND_TIMEOUT
+ )
+ require(launchButton != null) { "Can't find launch secondary activity button on screen." }
+ launchButton.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withActivityState(SECONDARY_ACTIVITY_COMPONENT, STATE_RESUMED)
+ .withActivityState(MAIN_ACTIVITY_COMPONENT, STATE_RESUMED)
+ .waitForAndVerify()
+ }
+
+ /**
* Clicks the button to launch the placeholder primary activity, which should launch the
* placeholder secondary activity based on the placeholder rule.
*/
@@ -63,6 +82,9 @@
val MAIN_ACTIVITY_COMPONENT =
ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT.toFlickerComponent()
+ val SECONDARY_ACTIVITY_COMPONENT =
+ ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent()
+
val PLACEHOLDER_PRIMARY_COMPONENT =
ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
.toFlickerComponent()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
index b40720b..4890bba 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
@@ -100,6 +100,7 @@
flicker.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
}
+ @Presubmit
@Test
@IwTest(focusArea = "ime")
override fun cujCompleted() {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
index f6838f4..63f7200 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
@@ -102,6 +102,7 @@
flicker.assertWm { this.isAppWindowOnTop(testApp) }
}
+ @Presubmit
@Test
@IwTest(focusArea = "ime")
override fun cujCompleted() {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index a744cd7..0b7b165 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -79,6 +79,7 @@
super.visibleLayersShownMoreThanOneConsecutiveEntry()
}
+ @Presubmit
@Test
@IwTest(focusArea = "ime")
override fun cujCompleted() {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
index 36747cc7..851651e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
@@ -50,6 +50,7 @@
}
}
+ @Presubmit
@Test
@IwTest(focusArea = "ime")
override fun cujCompleted() {
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index cd47f60..5361d73f 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -179,6 +179,13 @@
</intent-filter>
</activity>
<activity
+ android:name=".ActivityEmbeddingSecondaryActivity"
+ android:label="ActivityEmbedding Secondary"
+ 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=".ActivityEmbeddingPlaceholderPrimaryActivity"
android:label="ActivityEmbedding Placeholder Primary"
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index 19c81a8..d78b9a8 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -22,6 +22,14 @@
android:background="@android:color/holo_orange_light">
<Button
+ android:id="@+id/launch_secondary_activity_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_centerHorizontal="true"
+ android:onClick="launchSecondaryActivity"
+ android:text="Launch Secondary Activity" />
+
+ <Button
android:id="@+id/launch_placeholder_split_button"
android:layout_width="wrap_content"
android:layout_height="48dp"
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
index 04a590d..6a7a2cc 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
@@ -25,6 +25,7 @@
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
import androidx.window.extensions.embedding.EmbeddingRule;
+import androidx.window.extensions.embedding.SplitPairRule;
import androidx.window.extensions.embedding.SplitPlaceholderRule;
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper;
@@ -40,20 +41,23 @@
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_embedding_main_layout);
+ }
- initializeSplitRules();
+ /** R.id.launch_secondary_activity_button onClick */
+ public void launchSecondaryActivity(View view) {
+ initializeSplitRules(createSplitPairRules());
+ startActivity(new Intent().setComponent(
+ ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT));
}
/** R.id.launch_placeholder_split_button onClick */
public void launchPlaceholderSplit(View view) {
- startActivity(
- new Intent().setComponent(
- ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
- )
- );
+ initializeSplitRules(createSplitPlaceholderRules());
+ startActivity(new Intent().setComponent(
+ ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT));
}
- private void initializeSplitRules() {
+ private void initializeSplitRules(Set<EmbeddingRule> rules) {
ActivityEmbeddingComponent embeddingComponent =
ActivityEmbeddingAppHelper.getActivityEmbeddingComponent();
if (embeddingComponent == null) {
@@ -62,14 +66,28 @@
finish();
return;
}
-
- embeddingComponent.setEmbeddingRules(getSplitRules());
+ embeddingComponent.setEmbeddingRules(rules);
}
- private Set<EmbeddingRule> getSplitRules() {
+ private Set<EmbeddingRule> createSplitPairRules() {
final Set<EmbeddingRule> rules = new ArraySet<>();
+ final SplitPairRule rule = new SplitPairRule.Builder(
+ activitiesPair -> activitiesPair.first instanceof ActivityEmbeddingMainActivity
+ && activitiesPair.second instanceof ActivityEmbeddingSecondaryActivity,
+ activityIntentPair ->
+ activityIntentPair.first instanceof ActivityEmbeddingMainActivity
+ && activityIntentPair.second.getComponent().equals(ActivityOptions
+ .ActivityEmbedding.SecondaryActivity.COMPONENT),
+ windowMetrics -> true)
+ .setSplitRatio(DEFAULT_SPLIT_RATIO)
+ .build();
+ rules.add(rule);
+ return rules;
+ }
- final SplitPlaceholderRule placeholderRule = new SplitPlaceholderRule.Builder(
+ private Set<EmbeddingRule> createSplitPlaceholderRules() {
+ final Set<EmbeddingRule> rules = new ArraySet<>();
+ final SplitPlaceholderRule rule = new SplitPlaceholderRule.Builder(
new Intent().setComponent(
ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT),
activity -> activity instanceof ActivityEmbeddingPlaceholderPrimaryActivity,
@@ -78,7 +96,7 @@
windowMetrics -> true)
.setSplitRatio(DEFAULT_SPLIT_RATIO)
.build();
- rules.add(placeholderRule);
+ rules.add(rule);
return rules;
}
}
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
new file mode 100644
index 0000000..00f4c25
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
@@ -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.server.wm.flicker.testapp;
+
+import android.graphics.Color;
+
+/**
+ * Activity to be used as the secondary activity to split with
+ * {@link ActivityEmbeddingMainActivity}.
+ */
+public class ActivityEmbeddingSecondaryActivity extends ActivityEmbeddingBaseActivity {
+ @Override
+ int getBackgroundColor() {
+ return Color.YELLOW;
+ }
+}
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 00684de..b61bc0c 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
@@ -87,6 +87,12 @@
FLICKER_APP_PACKAGE + ".ActivityEmbeddingMainActivity");
}
+ public static class SecondaryActivity {
+ public static final String LABEL = "ActivityEmbeddingSecondaryActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ActivityEmbeddingSecondaryActivity");
+ }
+
public static class PlaceholderPrimaryActivity {
public static final String LABEL = "ActivityEmbeddingPlaceholderPrimaryActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS b/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS
new file mode 100644
index 0000000..2a4acc1
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 1216021
+
+asapperstein@google.com
+etancohen@google.com
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/OWNERS b/wifi/tests/src/android/net/wifi/sharedconnectivity/OWNERS
new file mode 100644
index 0000000..8873d07
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/OWNERS
@@ -0,0 +1 @@
+file:/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS