Merge "[flexiglass] Moves hardcoded enabledness check into SceneContainerFlags." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 6bd6c93..525ae80 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -19,6 +19,7 @@
// Add java_aconfig_libraries to here to add them to the core framework
srcs: [
":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+ ":android.content.pm.flags-aconfig-java{.generated_srcjars}",
":android.os.flags-aconfig-java{.generated_srcjars}",
":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
":android.security.flags-aconfig-java{.generated_srcjars}",
@@ -28,9 +29,11 @@
":com.android.hardware.input-aconfig-java{.generated_srcjars}",
":com.android.text.flags-aconfig-java{.generated_srcjars}",
":telecom_flags_core_java_lib{.generated_srcjars}",
+ ":telephony_flags_core_java_lib{.generated_srcjars}",
":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
":android.widget.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
],
// Add aconfig-annotations-lib as a dependency for the optimization
libs: ["aconfig-annotations-lib"],
@@ -58,6 +61,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Telephony
+java_aconfig_library {
+ name: "telephony_flags_core_java_lib",
+ aconfig_declarations: "telephony_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Window
aconfig_declarations {
name: "com.android.window.flags.window-aconfig",
@@ -214,3 +224,28 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Package Manager
+aconfig_declarations {
+ name: "android.content.pm.flags-aconfig",
+ package: "android.content.pm",
+ srcs: ["core/java/android/content/pm/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.content.pm.flags-aconfig-java",
+ aconfig_declarations: "android.content.pm.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Media BetterTogether
+aconfig_declarations {
+ name: "com.android.media.flags.bettertogether-aconfig",
+ package: "com.android.media.flags",
+ srcs: ["media/java/android/media/flags/media_better_together.aconfig"],
+}
+
+java_aconfig_library {
+ name: "com.android.media.flags.bettertogether-aconfig-java",
+ aconfig_declarations: "com.android.media.flags.bettertogether-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/OWNERS b/OWNERS
index 6c25324..4e5c7d8 100644
--- a/OWNERS
+++ b/OWNERS
@@ -28,7 +28,7 @@
# Support bulk translation updates
per-file */res*/values*/*.xml = byi@google.com, delphij@google.com
-per-file **.bp,**.mk = hansson@google.com, joeo@google.com
+per-file **.bp,**.mk = hansson@google.com, joeo@google.com, lamontjones@google.com
per-file TestProtoLibraries.bp = file:platform/platform_testing:/libraries/health/OWNERS
per-file TestProtoLibraries.bp = file:platform/tools/tradefederation:/OWNERS
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 66c1efc..24d815f 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -420,7 +420,7 @@
public static final int REASON_SYSTEM_EXEMPT_APP_OP = 327;
/**
- * Granted by {@link com.android.server.pm.PackageArchiverService} to the installer responsible
+ * Granted by {@link com.android.server.pm.PackageArchiver} to the installer responsible
* for unarchiving an app.
*
* @hide
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index dcc324d..5c60562 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -75,7 +75,7 @@
private static final String ALARM_TAG_AFFORDABILITY_CHECK = "*tare.affordability_check*";
private final Object mLock;
- private final Handler mHandler;
+ private final AgentHandler mHandler;
private final Analyst mAnalyst;
private final InternalResourceService mIrs;
private final Scribe mScribe;
@@ -992,6 +992,7 @@
void tearDownLocked() {
mCurrentOngoingEvents.clear();
mBalanceThresholdAlarmQueue.removeAllAlarms();
+ mHandler.removeAllMessages();
}
@VisibleForTesting
@@ -1290,6 +1291,11 @@
break;
}
}
+
+ void removeAllMessages() {
+ removeMessages(MSG_CHECK_ALL_AFFORDABILITY);
+ removeMessages(MSG_CHECK_INDIVIDUAL_AFFORDABILITY);
+ }
}
@GuardedBy("mLock")
diff --git a/core/api/current.txt b/core/api/current.txt
index d92c693..1879da5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -17653,9 +17653,13 @@
package android.graphics.text {
public final class LineBreakConfig {
+ method @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public int getHyphenation();
method public int getLineBreakStyle();
method public int getLineBreakWordStyle();
method @NonNull public android.graphics.text.LineBreakConfig merge(@NonNull android.graphics.text.LineBreakConfig);
+ field @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final int HYPHENATION_DISABLED = 0; // 0x0
+ field @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final int HYPHENATION_ENABLED = 1; // 0x1
+ field @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final int HYPHENATION_UNSPECIFIED = -1; // 0xffffffff
field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1
field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0
field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2
@@ -17670,6 +17674,7 @@
ctor public LineBreakConfig.Builder();
method @NonNull public android.graphics.text.LineBreakConfig build();
method @NonNull public android.graphics.text.LineBreakConfig.Builder merge(@NonNull android.graphics.text.LineBreakConfig);
+ method @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) @NonNull public android.graphics.text.LineBreakConfig.Builder setHyphenation(int);
method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakStyle(int);
method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakWordStyle(int);
}
@@ -23946,10 +23951,12 @@
method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers();
method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
+ method @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2) @Nullable public android.media.RouteListingPreference getRouteListingPreference();
method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes();
method @NonNull public android.media.MediaRouter2.RoutingController getSystemController();
method public void registerControllerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.ControllerCallback);
method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference);
+ method @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2) public void registerRouteListingPreferenceCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteListingPreferenceCallback);
method public void registerTransferCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.TransferCallback);
method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener);
method public void setRouteListingPreference(@Nullable android.media.RouteListingPreference);
@@ -23958,6 +23965,7 @@
method public void transferTo(@NonNull android.media.MediaRoute2Info);
method public void unregisterControllerCallback(@NonNull android.media.MediaRouter2.ControllerCallback);
method public void unregisterRouteCallback(@NonNull android.media.MediaRouter2.RouteCallback);
+ method @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2) public void unregisterRouteListingPreferenceCallback(@NonNull android.media.MediaRouter2.RouteListingPreferenceCallback);
method public void unregisterTransferCallback(@NonNull android.media.MediaRouter2.TransferCallback);
}
@@ -23978,6 +23986,11 @@
method public void onRoutesUpdated(@NonNull java.util.List<android.media.MediaRoute2Info>);
}
+ @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2) public abstract static class MediaRouter2.RouteListingPreferenceCallback {
+ ctor @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2) public MediaRouter2.RouteListingPreferenceCallback();
+ method @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2) public void onRouteListingPreferenceChanged(@Nullable android.media.RouteListingPreference);
+ }
+
public class MediaRouter2.RoutingController {
method public void deselectRoute(@NonNull android.media.MediaRoute2Info);
method @Nullable public android.os.Bundle getControlHints();
@@ -46573,9 +46586,9 @@
}
public class BoringLayout extends android.text.Layout implements android.text.TextUtils.EllipsizeCallback {
- ctor @Deprecated public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
- ctor @Deprecated public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
- ctor @Deprecated public BoringLayout(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, float, float, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
+ ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
+ ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
+ ctor public BoringLayout(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, float, float, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
method public void ellipsized(int, int);
method public int getBottomPadding();
method public int getEllipsisCount(int);
@@ -46588,12 +46601,12 @@
method public int getLineTop(int);
method public int getParagraphDirection(int);
method public int getTopPadding();
- method @Deprecated public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint);
- method @Deprecated public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint, android.text.BoringLayout.Metrics);
- method @Deprecated @Nullable public static android.text.BoringLayout.Metrics isBoring(@NonNull CharSequence, @NonNull android.text.TextPaint, @NonNull android.text.TextDirectionHeuristic, boolean, @Nullable android.text.BoringLayout.Metrics);
- method @Deprecated public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
- method @Deprecated public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
- method @Deprecated @NonNull public static android.text.BoringLayout make(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
+ method public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint);
+ method public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint, android.text.BoringLayout.Metrics);
+ method @Nullable public static android.text.BoringLayout.Metrics isBoring(@NonNull CharSequence, @NonNull android.text.TextPaint, @NonNull android.text.TextDirectionHeuristic, boolean, @Nullable android.text.BoringLayout.Metrics);
+ method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
+ method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
+ method @NonNull public static android.text.BoringLayout make(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
method @NonNull public android.text.BoringLayout replaceOrMake(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
@@ -46601,7 +46614,7 @@
public static class BoringLayout.Metrics extends android.graphics.Paint.FontMetricsInt {
ctor public BoringLayout.Metrics();
- method @NonNull public android.graphics.RectF getDrawingBoundingBox();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @NonNull public android.graphics.RectF getDrawingBoundingBox();
field public int width;
}
@@ -46786,7 +46799,7 @@
public abstract class Layout {
ctor protected Layout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float);
- method @NonNull public android.graphics.RectF computeDrawingBoundingBox();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @NonNull public android.graphics.RectF computeDrawingBoundingBox();
method public void draw(android.graphics.Canvas);
method public void draw(android.graphics.Canvas, android.graphics.Path, android.graphics.Paint, int);
method public void draw(@NonNull android.graphics.Canvas, @Nullable java.util.List<android.graphics.Path>, @Nullable java.util.List<android.graphics.Paint>, @Nullable android.graphics.Path, @Nullable android.graphics.Paint, int);
@@ -46795,24 +46808,24 @@
method public void fillCharacterBounds(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull float[], @IntRange(from=0) int);
method @NonNull public final android.text.Layout.Alignment getAlignment();
method public abstract int getBottomPadding();
- method public final int getBreakStrategy();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public final int getBreakStrategy();
method public void getCursorPath(int, android.graphics.Path, CharSequence);
method public static float getDesiredWidth(CharSequence, android.text.TextPaint);
method public static float getDesiredWidth(CharSequence, int, int, android.text.TextPaint);
method public abstract int getEllipsisCount(int);
method public abstract int getEllipsisStart(int);
- method @Nullable public final android.text.TextUtils.TruncateAt getEllipsize();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @Nullable public final android.text.TextUtils.TruncateAt getEllipsize();
method @IntRange(from=0) public int getEllipsizedWidth();
method public int getHeight();
- method public final int getHyphenationFrequency();
- method public final int getJustificationMode();
- method @Nullable public final int[] getLeftIndents();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public final int getHyphenationFrequency();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public final int getJustificationMode();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @Nullable public final int[] getLeftIndents();
method public final int getLineAscent(int);
method public final int getLineBaseline(int);
method public final int getLineBottom(int);
method public int getLineBottom(int, boolean);
method public int getLineBounds(int, android.graphics.Rect);
- method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
method public abstract boolean getLineContainsTab(int);
method public abstract int getLineCount();
method public abstract int getLineDescent(int);
@@ -46823,13 +46836,13 @@
method public float getLineLeft(int);
method public float getLineMax(int);
method public float getLineRight(int);
- method public final float getLineSpacingAmount();
- method public final float getLineSpacingMultiplier();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public final float getLineSpacingAmount();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public final float getLineSpacingMultiplier();
method public abstract int getLineStart(int);
method public abstract int getLineTop(int);
method public int getLineVisibleEnd(int);
method public float getLineWidth(int);
- method @IntRange(from=1) public final int getMaxLines();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @IntRange(from=1) public final int getMaxLines();
method public int getOffsetForHorizontal(int, float);
method public int getOffsetToLeftOf(int);
method public int getOffsetToRightOf(int);
@@ -46840,19 +46853,19 @@
method public final int getParagraphRight(int);
method public float getPrimaryHorizontal(int);
method @Nullable public int[] getRangeForRect(@NonNull android.graphics.RectF, @NonNull android.text.SegmentFinder, @NonNull android.text.Layout.TextInclusionStrategy);
- method @Nullable public final int[] getRightIndents();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @Nullable public final int[] getRightIndents();
method public float getSecondaryHorizontal(int);
method public void getSelectionPath(int, int, android.graphics.Path);
method public final float getSpacingAdd();
method public final float getSpacingMultiplier();
- method @NonNull public final CharSequence getText();
- method @NonNull public final android.text.TextDirectionHeuristic getTextDirectionHeuristic();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @NonNull public final CharSequence getText();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @NonNull public final android.text.TextDirectionHeuristic getTextDirectionHeuristic();
method public abstract int getTopPadding();
- method public boolean getUseBoundsForWidth();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public boolean getUseBoundsForWidth();
method @IntRange(from=0) public final int getWidth();
method public final void increaseWidthTo(int);
method public boolean isFallbackLineSpacingEnabled();
- method public final boolean isFontPaddingIncluded();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public final boolean isFontPaddingIncluded();
method public boolean isRtlCharAt(int);
method protected final boolean isSpanned();
field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
@@ -46880,7 +46893,7 @@
enum_constant public static final android.text.Layout.Alignment ALIGN_OPPOSITE;
}
- public static final class Layout.Builder {
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public static final class Layout.Builder {
ctor public Layout.Builder(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.text.TextPaint, @IntRange(from=0) int);
method @NonNull public android.text.Layout build();
method @NonNull public android.text.Layout.Builder setAlignment(@NonNull android.text.Layout.Alignment);
@@ -46898,7 +46911,7 @@
method @NonNull public android.text.Layout.Builder setMaxLines(@IntRange(from=1) int);
method @NonNull public android.text.Layout.Builder setRightIndents(@Nullable int[]);
method @NonNull public android.text.Layout.Builder setTextDirectionHeuristic(@NonNull android.text.TextDirectionHeuristic);
- method @NonNull public android.text.Layout.Builder setUseBoundsForWidth(boolean);
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @NonNull public android.text.Layout.Builder setUseBoundsForWidth(boolean);
}
public static class Layout.Directions {
@@ -47875,11 +47888,15 @@
method public void writeToParcel(@NonNull android.os.Parcel, int);
}
- public class LineBreakConfigSpan {
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public class LineBreakConfigSpan {
ctor public LineBreakConfigSpan(@NonNull android.graphics.text.LineBreakConfig);
method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
}
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final class LineBreakConfigSpan.NoHyphenationSpan extends android.text.style.LineBreakConfigSpan {
+ ctor @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public LineBreakConfigSpan.NoHyphenationSpan();
+ }
+
public interface LineHeightSpan extends android.text.style.ParagraphStyle android.text.style.WrapTogetherSpan {
method public void chooseHeight(CharSequence, int, int, int, int, android.graphics.Paint.FontMetricsInt);
}
@@ -59871,7 +59888,7 @@
method public final android.text.method.TransformationMethod getTransformationMethod();
method public android.graphics.Typeface getTypeface();
method public android.text.style.URLSpan[] getUrls();
- method public boolean getUseBoundsForWidth();
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public boolean getUseBoundsForWidth();
method public boolean hasSelection();
method public boolean isAllCaps();
method public boolean isCursorVisible();
@@ -60014,7 +60031,7 @@
method public final void setTransformationMethod(android.text.method.TransformationMethod);
method public void setTypeface(@Nullable android.graphics.Typeface, int);
method public void setTypeface(@Nullable android.graphics.Typeface);
- method public void setUseBoundsForWidth(boolean);
+ method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public void setUseBoundsForWidth(boolean);
method public void setWidth(int);
field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a5e69ab..fcfa41a6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3192,6 +3192,7 @@
public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
+ method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void addActivityPolicyExemption(@NonNull android.content.ComponentName);
method public void addSoundEffectListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
method @NonNull public android.content.Context createContext();
@@ -3212,6 +3213,7 @@
method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
+ method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
@@ -3243,6 +3245,7 @@
field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+ field @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
field public static final int POLICY_TYPE_AUDIO = 1; // 0x1
field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
@@ -3795,13 +3798,6 @@
field @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800
}
- public class PackageArchiver {
- method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
- method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
- field public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
- field public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
- }
-
public class PackageInfo implements android.os.Parcelable {
field public boolean isArchived;
}
@@ -3809,6 +3805,8 @@
public class PackageInstaller {
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
+ method @FlaggedApi(Flags.FLAG_ARCHIVING) @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @FlaggedApi(Flags.FLAG_ARCHIVING) @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
@@ -3819,6 +3817,8 @@
field public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
field public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH";
+ field @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
+ field @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
field public static final int LOCATION_DATA_APP = 0; // 0x0
field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
@@ -3903,7 +3903,6 @@
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
method @Deprecated @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
- method @NonNull public android.content.pm.PackageArchiver getPackageArchiver();
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public int getPackageUidAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]);
@@ -3929,6 +3928,7 @@
method @RequiresPermission(android.Manifest.permission.SET_HARMFUL_APP_WARNINGS) public void setHarmfulAppWarning(@NonNull String, @Nullable CharSequence);
method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable String);
method @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo);
+ method @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED) @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo, int);
method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setSyntheticAppDetailsActivityEnabled(@NonNull String, boolean);
method public void setSystemAppState(@NonNull String, int);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean);
@@ -3979,6 +3979,7 @@
field public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 512; // 0x200
field public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED = 256; // 0x100
field public static final int FLAG_PERMISSION_USER_SET = 1; // 0x1
+ field @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED) public static final int FLAG_SUSPEND_QUARANTINED = 1; // 0x1
field public static final int INSTALL_FAILED_ALREADY_EXISTS = -1; // 0xffffffff
field public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13; // 0xfffffff3
field public static final int INSTALL_FAILED_CONTAINER_ERROR = -18; // 0xffffffee
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 70cb597..13a1bd6 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -9,6 +9,11 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+aidl_library {
+ name: "HardwareBuffer_aidl",
+ hdrs: ["android/hardware/HardwareBuffer.aidl"],
+}
+
filegroup {
name: "framework-core-sources",
srcs: [
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 2ae7216..895dde7 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1507,8 +1507,9 @@
}
/** @hide */
- public void setRemoteTransition(@Nullable RemoteTransition remoteTransition) {
+ public ActivityOptions setRemoteTransition(@Nullable RemoteTransition remoteTransition) {
mRemoteTransition = remoteTransition;
+ return this;
}
/** @hide */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 39589fa..f28b4b4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -36,7 +36,6 @@
import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
import static android.window.ConfigurationHelper.isDifferentDisplay;
import static android.window.ConfigurationHelper.shouldUpdateResources;
-
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
@@ -1286,8 +1285,13 @@
}
private void updateCompatOverrideScale(CompatibilityInfo info) {
- CompatibilityInfo.setOverrideInvertedScale(
- info.hasOverrideScaling() ? info.applicationInvertedScale : 1f);
+ if (info.hasOverrideScaling()) {
+ CompatibilityInfo.setOverrideInvertedScale(info.applicationInvertedScale,
+ info.applicationDensityInvertedScale);
+ } else {
+ CompatibilityInfo.setOverrideInvertedScale(/* invertScale */ 1f,
+ /* densityInvertScale */1f);
+ }
}
public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index fcd13b8..0255860 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -64,7 +64,6 @@
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
import android.content.pm.ModuleInfo;
-import android.content.pm.PackageArchiver;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageItemInfo;
@@ -173,7 +172,6 @@
private volatile UserManager mUserManager;
private volatile PermissionManager mPermissionManager;
private volatile PackageInstaller mInstaller;
- private volatile PackageArchiver mPackageArchiver;
private volatile ArtManager mArtManager;
private volatile DevicePolicyManager mDevicePolicyManager;
private volatile String mPermissionsControllerPackageName;
@@ -3284,18 +3282,6 @@
}
@Override
- public PackageArchiver getPackageArchiver() {
- if (mPackageArchiver == null) {
- try {
- mPackageArchiver = new PackageArchiver(mContext, mPM.getPackageArchiverService());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- return mPackageArchiver;
- }
-
- @Override
public boolean isPackageAvailable(String packageName) {
try {
return mPM.isPackageAvailable(packageName, getUserId());
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index c58561d..bf00a5a 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -23,6 +23,7 @@
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.content.ComponentName;
import android.content.IntentFilter;
import android.graphics.Point;
import android.graphics.PointF;
@@ -86,6 +87,18 @@
void setDevicePolicy(int policyType, int devicePolicy);
/**
+ * Adds an exemption to the default activity launch policy.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void addActivityPolicyExemption(in ComponentName exemption);
+
+ /**
+ * Removes an exemption to the default activity launch policy.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void removeActivityPolicyExemption(in ComponentName exemption);
+
+ /**
* Notifies that an audio session being started.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 2e5c0f7..7bf2e91 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -247,6 +247,22 @@
}
}
+ void addActivityPolicyExemption(@NonNull ComponentName componentName) {
+ try {
+ mVirtualDevice.addActivityPolicyExemption(componentName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
+ try {
+ mVirtualDevice.removeActivityPolicyExemption(componentName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@NonNull
VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
try {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 7b81031..d338d17 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -62,7 +62,6 @@
import android.view.Surface;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.AnnotationValidations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -624,19 +623,62 @@
* @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
*
* @see VirtualDeviceParams#POLICY_TYPE_RECENTS
+ * @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY
*/
@FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
@VirtualDeviceParams.DevicePolicy int devicePolicy) {
- AnnotationValidations.validate(
- VirtualDeviceParams.DynamicPolicyType.class, null, policyType);
- AnnotationValidations.validate(
- VirtualDeviceParams.DevicePolicy.class, null, devicePolicy);
mVirtualDeviceInternal.setDevicePolicy(policyType, devicePolicy);
}
/**
+ * Specifies a component name to be exempt from the current activity launch policy.
+ *
+ * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVIY} allows activity
+ * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT},
+ * then the specified component will be blocked from launching.
+ * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity
+ * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}, then
+ * the specified component will be allowed to launch.</p>
+ *
+ * <p>Note that changing the activity launch policy will not affect current set of exempt
+ * components and it needs to be updated separately.</p>
+ *
+ * @see #removeActivityPolicyExemption
+ * @see #setDevicePolicy
+ */
+ @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void addActivityPolicyExemption(@NonNull ComponentName componentName) {
+ mVirtualDeviceInternal.addActivityPolicyExemption(
+ Objects.requireNonNull(componentName));
+ }
+
+ /**
+ * Makes the specified component name to adhere to the default activity launch policy.
+ *
+ * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVIY} allows activity
+ * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT},
+ * then the specified component will be allowed to launch.
+ * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity
+ * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}, then
+ * the specified component will be blocked from launching.</p>
+ *
+ * <p>Note that changing the activity launch policy will not affect current set of exempt
+ * components and it needs to be updated separately.</p>
+ *
+ * @see #addActivityPolicyExemption
+ * @see #setDevicePolicy
+ */
+ @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
+ mVirtualDeviceInternal.removeActivityPolicyExemption(
+ Objects.requireNonNull(componentName));
+ }
+
+ /**
* Creates a virtual dpad.
*
* @param config the configurations of the virtual dpad.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 51df257..b4c740ec 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -22,12 +22,14 @@
import static java.util.concurrent.TimeUnit.MICROSECONDS;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
import android.companion.virtual.sensor.IVirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorCallback;
@@ -144,7 +146,7 @@
* @hide
*/
@IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO,
- POLICY_TYPE_RECENTS})
+ POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface PolicyType {}
@@ -155,7 +157,7 @@
* @see VirtualDeviceManager.VirtualDevice#setDevicePolicy
* @hide
*/
- @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_RECENTS})
+ @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface DynamicPolicyType {}
@@ -195,19 +197,35 @@
* <li>{@link #DEVICE_POLICY_DEFAULT}: Activities launched on VirtualDisplays owned by this
* device will appear in the host device recents.
* <li>{@link #DEVICE_POLICY_CUSTOM}: Activities launched on VirtualDisplays owned by this
- * * device will not appear in recents.
+ * device will not appear in recents.
* </ul>
*/
public static final int POLICY_TYPE_RECENTS = 2;
+ /**
+ * Tells the activity manager what the default launch behavior for activities on this device is.
+ *
+ * <ul>
+ * <li>{@link #DEVICE_POLICY_DEFAULT}: Activities are allowed to be launched on displays
+ * owned by this device, unless explicitly blocked by the device.
+ * <li>{@link #DEVICE_POLICY_CUSTOM}: Activities are blocked from launching on displays
+ * owned by this device, unless explicitly allowed by the device.
+ * </ul>
+ *
+ * @see VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption
+ * @see VirtualDeviceManager.VirtualDevice#removeActivityPolicyExemption
+ */
+ @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
+ public static final int POLICY_TYPE_ACTIVITY = 3;
+
private final int mLockState;
@NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
@NavigationPolicy
private final int mDefaultNavigationPolicy;
- @NonNull private final ArraySet<ComponentName> mCrossTaskNavigationExceptions;
+ @NonNull private final ArraySet<ComponentName> mCrossTaskNavigationExemptions;
@ActivityPolicy
private final int mDefaultActivityPolicy;
- @NonNull private final ArraySet<ComponentName> mActivityPolicyExceptions;
+ @NonNull private final ArraySet<ComponentName> mActivityPolicyExemptions;
@Nullable private final String mName;
// Mapping of @PolicyType to @DevicePolicy
@NonNull private final SparseIntArray mDevicePolicies;
@@ -220,9 +238,9 @@
@LockState int lockState,
@NonNull Set<UserHandle> usersWithMatchingAccounts,
@NavigationPolicy int defaultNavigationPolicy,
- @NonNull Set<ComponentName> crossTaskNavigationExceptions,
+ @NonNull Set<ComponentName> crossTaskNavigationExemptions,
@ActivityPolicy int defaultActivityPolicy,
- @NonNull Set<ComponentName> activityPolicyExceptions,
+ @NonNull Set<ComponentName> activityPolicyExemptions,
@Nullable String name,
@NonNull SparseIntArray devicePolicies,
@NonNull List<VirtualSensorConfig> virtualSensorConfigs,
@@ -233,11 +251,11 @@
mUsersWithMatchingAccounts =
new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
mDefaultNavigationPolicy = defaultNavigationPolicy;
- mCrossTaskNavigationExceptions =
- new ArraySet<>(Objects.requireNonNull(crossTaskNavigationExceptions));
+ mCrossTaskNavigationExemptions =
+ new ArraySet<>(Objects.requireNonNull(crossTaskNavigationExemptions));
mDefaultActivityPolicy = defaultActivityPolicy;
- mActivityPolicyExceptions =
- new ArraySet<>(Objects.requireNonNull(activityPolicyExceptions));
+ mActivityPolicyExemptions =
+ new ArraySet<>(Objects.requireNonNull(activityPolicyExemptions));
mName = name;
mDevicePolicies = Objects.requireNonNull(devicePolicies);
mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
@@ -251,9 +269,9 @@
mLockState = parcel.readInt();
mUsersWithMatchingAccounts = (ArraySet<UserHandle>) parcel.readArraySet(null);
mDefaultNavigationPolicy = parcel.readInt();
- mCrossTaskNavigationExceptions = (ArraySet<ComponentName>) parcel.readArraySet(null);
+ mCrossTaskNavigationExemptions = (ArraySet<ComponentName>) parcel.readArraySet(null);
mDefaultActivityPolicy = parcel.readInt();
- mActivityPolicyExceptions = (ArraySet<ComponentName>) parcel.readArraySet(null);
+ mActivityPolicyExemptions = (ArraySet<ComponentName>) parcel.readArraySet(null);
mName = parcel.readString8();
mDevicePolicies = parcel.readSparseIntArray();
mVirtualSensorConfigs = new ArrayList<>();
@@ -295,7 +313,7 @@
public Set<ComponentName> getAllowedCrossTaskNavigations() {
return mDefaultNavigationPolicy == NAVIGATION_POLICY_DEFAULT_ALLOWED
? Collections.emptySet()
- : Collections.unmodifiableSet(mCrossTaskNavigationExceptions);
+ : Collections.unmodifiableSet(mCrossTaskNavigationExemptions);
}
/**
@@ -310,7 +328,7 @@
public Set<ComponentName> getBlockedCrossTaskNavigations() {
return mDefaultNavigationPolicy == NAVIGATION_POLICY_DEFAULT_BLOCKED
? Collections.emptySet()
- : Collections.unmodifiableSet(mCrossTaskNavigationExceptions);
+ : Collections.unmodifiableSet(mCrossTaskNavigationExemptions);
}
/**
@@ -336,7 +354,7 @@
public Set<ComponentName> getAllowedActivities() {
return mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_ALLOWED
? Collections.emptySet()
- : Collections.unmodifiableSet(mActivityPolicyExceptions);
+ : Collections.unmodifiableSet(mActivityPolicyExemptions);
}
/**
@@ -349,7 +367,7 @@
public Set<ComponentName> getBlockedActivities() {
return mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED
? Collections.emptySet()
- : Collections.unmodifiableSet(mActivityPolicyExceptions);
+ : Collections.unmodifiableSet(mActivityPolicyExemptions);
}
/**
@@ -440,9 +458,9 @@
dest.writeInt(mLockState);
dest.writeArraySet(mUsersWithMatchingAccounts);
dest.writeInt(mDefaultNavigationPolicy);
- dest.writeArraySet(mCrossTaskNavigationExceptions);
+ dest.writeArraySet(mCrossTaskNavigationExemptions);
dest.writeInt(mDefaultActivityPolicy);
- dest.writeArraySet(mActivityPolicyExceptions);
+ dest.writeArraySet(mActivityPolicyExemptions);
dest.writeString8(mName);
dest.writeSparseIntArray(mDevicePolicies);
dest.writeTypedList(mVirtualSensorConfigs);
@@ -476,9 +494,9 @@
return mLockState == that.mLockState
&& mUsersWithMatchingAccounts.equals(that.mUsersWithMatchingAccounts)
&& Objects.equals(
- mCrossTaskNavigationExceptions, that.mCrossTaskNavigationExceptions)
+ mCrossTaskNavigationExemptions, that.mCrossTaskNavigationExemptions)
&& mDefaultNavigationPolicy == that.mDefaultNavigationPolicy
- && Objects.equals(mActivityPolicyExceptions, that.mActivityPolicyExceptions)
+ && Objects.equals(mActivityPolicyExemptions, that.mActivityPolicyExemptions)
&& mDefaultActivityPolicy == that.mDefaultActivityPolicy
&& Objects.equals(mName, that.mName)
&& mAudioPlaybackSessionId == that.mAudioPlaybackSessionId
@@ -488,8 +506,8 @@
@Override
public int hashCode() {
int hashCode = Objects.hash(
- mLockState, mUsersWithMatchingAccounts, mCrossTaskNavigationExceptions,
- mDefaultNavigationPolicy, mActivityPolicyExceptions, mDefaultActivityPolicy, mName,
+ mLockState, mUsersWithMatchingAccounts, mCrossTaskNavigationExemptions,
+ mDefaultNavigationPolicy, mActivityPolicyExemptions, mDefaultActivityPolicy, mName,
mDevicePolicies, mAudioPlaybackSessionId, mAudioRecordingSessionId);
for (int i = 0; i < mDevicePolicies.size(); i++) {
hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
@@ -505,9 +523,9 @@
+ " mLockState=" + mLockState
+ " mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts
+ " mDefaultNavigationPolicy=" + mDefaultNavigationPolicy
- + " mCrossTaskNavigationExceptions=" + mCrossTaskNavigationExceptions
+ + " mCrossTaskNavigationExemptions=" + mCrossTaskNavigationExemptions
+ " mDefaultActivityPolicy=" + mDefaultActivityPolicy
- + " mActivityPolicyExceptions=" + mActivityPolicyExceptions
+ + " mActivityPolicyExemptions=" + mActivityPolicyExemptions
+ " mName=" + mName
+ " mDevicePolicies=" + mDevicePolicies
+ " mAudioPlaybackSessionId=" + mAudioPlaybackSessionId
@@ -524,9 +542,9 @@
pw.println(prefix + "mLockState=" + mLockState);
pw.println(prefix + "mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts);
pw.println(prefix + "mDefaultNavigationPolicy=" + mDefaultNavigationPolicy);
- pw.println(prefix + "mCrossTaskNavigationExceptions=" + mCrossTaskNavigationExceptions);
+ pw.println(prefix + "mCrossTaskNavigationExemptions=" + mCrossTaskNavigationExemptions);
pw.println(prefix + "mDefaultActivityPolicy=" + mDefaultActivityPolicy);
- pw.println(prefix + "mActivityPolicyExceptions=" + mActivityPolicyExceptions);
+ pw.println(prefix + "mActivityPolicyExemptions=" + mActivityPolicyExemptions);
pw.println(prefix + "mDevicePolicies=" + mDevicePolicies);
pw.println(prefix + "mVirtualSensorConfigs=" + mVirtualSensorConfigs);
pw.println(prefix + "mAudioPlaybackSessionId=" + mAudioPlaybackSessionId);
@@ -552,11 +570,11 @@
private @LockState int mLockState = LOCK_STATE_DEFAULT;
@NonNull private Set<UserHandle> mUsersWithMatchingAccounts = Collections.emptySet();
- @NonNull private Set<ComponentName> mCrossTaskNavigationExceptions = Collections.emptySet();
+ @NonNull private Set<ComponentName> mCrossTaskNavigationExemptions = Collections.emptySet();
@NavigationPolicy
private int mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED;
private boolean mDefaultNavigationPolicyConfigured = false;
- @NonNull private Set<ComponentName> mActivityPolicyExceptions = Collections.emptySet();
+ @NonNull private Set<ComponentName> mActivityPolicyExemptions = Collections.emptySet();
@ActivityPolicy
private int mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
private boolean mDefaultActivityPolicyConfigured = false;
@@ -700,7 +718,7 @@
}
mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_BLOCKED;
mDefaultNavigationPolicyConfigured = true;
- mCrossTaskNavigationExceptions = Objects.requireNonNull(allowedCrossTaskNavigations);
+ mCrossTaskNavigationExemptions = Objects.requireNonNull(allowedCrossTaskNavigations);
return this;
}
@@ -731,7 +749,7 @@
}
mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED;
mDefaultNavigationPolicyConfigured = true;
- mCrossTaskNavigationExceptions = Objects.requireNonNull(blockedCrossTaskNavigations);
+ mCrossTaskNavigationExemptions = Objects.requireNonNull(blockedCrossTaskNavigations);
return this;
}
@@ -757,7 +775,7 @@
}
mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_BLOCKED;
mDefaultActivityPolicyConfigured = true;
- mActivityPolicyExceptions = Objects.requireNonNull(allowedActivities);
+ mActivityPolicyExemptions = Objects.requireNonNull(allowedActivities);
return this;
}
@@ -783,7 +801,7 @@
}
mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
mDefaultActivityPolicyConfigured = true;
- mActivityPolicyExceptions = Objects.requireNonNull(blockedActivities);
+ mActivityPolicyExemptions = Objects.requireNonNull(blockedActivities);
return this;
}
@@ -956,6 +974,35 @@
mVirtualSensorDirectChannelCallback);
}
+ if (Flags.dynamicPolicy()) {
+ switch (mDevicePolicies.get(POLICY_TYPE_ACTIVITY, -1)) {
+ case DEVICE_POLICY_DEFAULT:
+ if (mDefaultActivityPolicyConfigured
+ && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED) {
+ throw new IllegalArgumentException(
+ "DEVICE_POLICY_DEFAULT is explicitly configured for "
+ + "POLICY_TYPE_ACTIVITY, which is exclusive with "
+ + "setAllowedActivities.");
+ }
+ break;
+ case DEVICE_POLICY_CUSTOM:
+ if (mDefaultActivityPolicyConfigured
+ && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_ALLOWED) {
+ throw new IllegalArgumentException(
+ "DEVICE_POLICY_CUSTOM is explicitly configured for "
+ + "POLICY_TYPE_ACTIVITY, which is exclusive with "
+ + "setBlockedActivities.");
+ }
+ break;
+ default:
+ if (mDefaultActivityPolicyConfigured
+ && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED) {
+ mDevicePolicies.put(POLICY_TYPE_ACTIVITY, DEVICE_POLICY_CUSTOM);
+ }
+ break;
+ }
+ }
+
if ((mAudioPlaybackSessionId != AUDIO_SESSION_ID_GENERATE
|| mAudioRecordingSessionId != AUDIO_SESSION_ID_GENERATE)
&& mDevicePolicies.get(POLICY_TYPE_AUDIO, DEVICE_POLICY_DEFAULT)
@@ -964,7 +1011,7 @@
+ "required for configuration of device-specific audio session ids.");
}
- SparseArray<Set<String>> sensorNameByType = new SparseArray();
+ SparseArray<Set<String>> sensorNameByType = new SparseArray<>();
for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) {
VirtualSensorConfig config = mVirtualSensorConfigs.get(i);
Set<String> sensorNames = sensorNameByType.get(config.getType(), new ArraySet<>());
@@ -979,9 +1026,9 @@
mLockState,
mUsersWithMatchingAccounts,
mDefaultNavigationPolicy,
- mCrossTaskNavigationExceptions,
+ mCrossTaskNavigationExemptions,
mDefaultActivityPolicy,
- mActivityPolicyExceptions,
+ mActivityPolicyExemptions,
mName,
mDevicePolicies,
mVirtualSensorConfigs,
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 3a3ab24..ee36f18 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -27,4 +27,3 @@
description: "Enable Virtual Camera"
bug: "270352264"
}
-
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index f156878..e9bbed3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5274,7 +5274,7 @@
* Broadcast Action: Sent to the responsible installer of an archived package when unarchival
* is requested.
*
- * @see android.content.pm.PackageArchiver
+ * @see android.content.pm.PackageInstaller#requestUnarchive(String)
* @hide
*/
@SystemApi
diff --git a/core/java/android/content/pm/IPackageArchiverService.aidl b/core/java/android/content/pm/IPackageArchiverService.aidl
deleted file mode 100644
index dc6491d..0000000
--- a/core/java/android/content/pm/IPackageArchiverService.aidl
+++ /dev/null
@@ -1,29 +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 android.content.pm;
-
-import android.content.IntentSender;
-import android.os.UserHandle;
-
-/** {@hide} */
-interface IPackageArchiverService {
-
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
- void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
-
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
- void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle);
-}
\ No newline at end of file
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index ebe2aa3..edb07ce 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -24,6 +24,7 @@
import android.content.pm.VersionedPackage;
import android.content.IntentSender;
import android.os.RemoteCallback;
+import android.os.UserHandle;
import android.graphics.Bitmap;
@@ -75,4 +76,10 @@
void waitForInstallConstraints(String installerPackageName, in List<String> packageNames,
in PackageInstaller.InstallConstraints constraints, in IntentSender callback,
long timeout);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
+ void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
+ void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle);
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 916c249..556c794 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -26,7 +26,6 @@
import android.content.pm.ChangedPackages;
import android.content.pm.InstantAppInfo;
import android.content.pm.FeatureInfo;
-import android.content.pm.IPackageArchiverService;
import android.content.pm.IDexModuleRegisterCallback;
import android.content.pm.InstallSourceInfo;
import android.content.pm.IOnChecksumsReadyListener;
@@ -652,8 +651,6 @@
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
IPackageInstaller getPackageInstaller();
- IPackageArchiverService getPackageArchiverService();
-
@EnforcePermission("DELETE_PACKAGES")
boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId);
@UnsupportedAppUsage
diff --git a/core/java/android/content/pm/PackageArchiver.java b/core/java/android/content/pm/PackageArchiver.java
deleted file mode 100644
index b065231..0000000
--- a/core/java/android/content/pm/PackageArchiver.java
+++ /dev/null
@@ -1,129 +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 android.content.pm;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
-import android.content.Context;
-import android.content.IntentSender;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.ParcelableException;
-import android.os.RemoteException;
-
-/**
- * {@code ArchiveManager} is used to archive apps. During the archival process, the apps APKs and
- * cache are removed from the device while the user data is kept. Through the
- * {@code requestUnarchive()} call, apps can be restored again through their responsible app store.
- *
- * <p> Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and
- * will be displayed to users with UI treatment to highlight that said apps are archived. If
- * a user taps on an archived app, the app will be unarchived and the restoration process is
- * communicated.
- *
- * @hide
- */
-// TODO(b/278560219) Improve public documentation.
-@SystemApi
-public class PackageArchiver {
-
- /**
- * Extra field for the package name of a package that is requested to be unarchived. Sent as
- * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent.
- *
- * @hide
- */
- @SystemApi
- public static final String EXTRA_UNARCHIVE_PACKAGE_NAME =
- "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
-
- /**
- * If true, the requestor of the unarchival has specified that the app should be unarchived
- * for {@link android.os.UserHandle#ALL}.
- *
- * @hide
- */
- @SystemApi
- public static final String EXTRA_UNARCHIVE_ALL_USERS =
- "android.content.pm.extra.UNARCHIVE_ALL_USERS";
-
- private final Context mContext;
- private final IPackageArchiverService mService;
-
- /**
- * @hide
- */
- public PackageArchiver(Context context, IPackageArchiverService service) {
- mContext = context;
- mService = service;
- }
-
- /**
- * Requests to archive a package which is currently installed.
- *
- * @param statusReceiver Callback used to notify when the operation is completed.
- * @throws NameNotFoundException If {@code packageName} isn't found or not available to the
- * caller or isn't archived.
- * @hide
- */
- @RequiresPermission(anyOf = {
- Manifest.permission.DELETE_PACKAGES,
- Manifest.permission.REQUEST_DELETE_PACKAGES})
- @SystemApi
- public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
- throws NameNotFoundException {
- try {
- mService.requestArchive(packageName, mContext.getPackageName(), statusReceiver,
- mContext.getUser());
- } catch (ParcelableException e) {
- e.maybeRethrow(NameNotFoundException.class);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Requests to unarchive a currently archived package.
- *
- * <p> Sends a request to unarchive an app to the responsible installer. The installer is
- * determined by {@link InstallSourceInfo#getUpdateOwnerPackageName()}, or
- * {@link InstallSourceInfo#getInstallingPackageName()} if the former value is null.
- *
- * <p> The installation will happen asynchronously and can be observed through
- * {@link android.content.Intent#ACTION_PACKAGE_ADDED}.
- *
- * @throws NameNotFoundException If {@code packageName} isn't found or not visible to the
- * caller or if the package has no installer on the device
- * anymore to unarchive it.
- * @hide
- */
- @RequiresPermission(anyOf = {
- Manifest.permission.INSTALL_PACKAGES,
- Manifest.permission.REQUEST_INSTALL_PACKAGES})
- @SystemApi
- public void requestUnarchive(@NonNull String packageName)
- throws NameNotFoundException {
- try {
- mService.requestUnarchive(packageName, mContext.getPackageName(), mContext.getUser());
- } catch (ParcelableException e) {
- e.maybeRethrow(NameNotFoundException.class);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index cdb8b46..1fe1923 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.IntentSender;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -492,7 +493,8 @@
/**
* Whether the package is currently in an archived state.
*
- * <p>Packages can be archived through {@link PackageArchiver} and do not have any APKs stored
+ * <p>Packages can be archived through
+ * {@link PackageInstaller#requestArchive(String, IntentSender)} and do not have any APKs stored
* on the device, but do keep the data directory.
* @hide
*/
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index f417480..390efd4 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -34,6 +34,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.DurationMillisLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -348,6 +349,28 @@
"android.content.pm.extra.RESOLVED_BASE_PATH";
/**
+ * Extra field for the package name of a package that is requested to be unarchived. Sent as
+ * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final String EXTRA_UNARCHIVE_PACKAGE_NAME =
+ "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+
+ /**
+ * If true, the requestor of the unarchival has specified that the app should be unarchived
+ * for {@link android.os.UserHandle#ALL}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final String EXTRA_UNARCHIVE_ALL_USERS =
+ "android.content.pm.extra.UNARCHIVE_ALL_USERS";
+
+ /**
* Streaming installation pending.
* Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
*
@@ -2158,6 +2181,72 @@
return new InstallInfo(result);
}
+ /**
+ * Requests to archive a package which is currently installed.
+ *
+ * <p> During the archival process, the apps APKs and cache are removed from the device while
+ * the user data is kept. Through the {@link #requestUnarchive(String)} call, apps can be
+ * restored again through their responsible installer.
+ *
+ * <p> Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and
+ * will be displayed to users with UI treatment to highlight that said apps are archived. If
+ * a user taps on an archived app, the app will be unarchived and the restoration process is
+ * communicated.
+ *
+ * @param statusReceiver Callback used to notify when the operation is completed.
+ * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
+ * available to the caller or isn't archived.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.REQUEST_DELETE_PACKAGES})
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
+ throws PackageManager.NameNotFoundException {
+ try {
+ mInstaller.requestArchive(packageName, mInstallerPackageName, statusReceiver,
+ new UserHandle(mUserId));
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests to unarchive a currently archived package.
+ *
+ * <p> Sends a request to unarchive an app to the responsible installer. The installer is
+ * determined by {@link InstallSourceInfo#getUpdateOwnerPackageName()}, or
+ * {@link InstallSourceInfo#getInstallingPackageName()} if the former value is null.
+ *
+ * <p> The installation will happen asynchronously and can be observed through
+ * {@link android.content.Intent#ACTION_PACKAGE_ADDED}.
+ *
+ * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
+ * visible to the caller or if the package has no
+ * installer on the device anymore to unarchive it.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.REQUEST_INSTALL_PACKAGES})
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public void requestUnarchive(@NonNull String packageName)
+ throws PackageManager.NameNotFoundException {
+ try {
+ mInstaller.requestUnarchive(packageName, mInstallerPackageName,
+ new UserHandle(mUserId));
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
// (b/239722738) This class serves as a bridge between the PackageLite class, which
// is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java)
// This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9a53a2a6..cdab431 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -20,6 +20,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.CheckResult;
import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.LongDef;
@@ -29,6 +30,7 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.StringRes;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
@@ -1238,8 +1240,9 @@
/**
* Flag parameter to also retrieve some information about archived packages.
- * Packages can be archived through {@link PackageArchiver} and do not have any APKs stored on
- * the device, but do keep the data directory.
+ * Packages can be archived through
+ * {@link PackageInstaller#requestArchive(String, IntentSender)} and do not have any APKs stored
+ * on the device, but do keep the data directory.
* <p> Note: Archived apps are a subset of apps returned by {@link #MATCH_UNINSTALLED_PACKAGES}.
* <p> Note: this flag may cause less information about currently installed
* applications to be returned.
@@ -1754,6 +1757,8 @@
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
public static final int FLAG_SUSPEND_QUARANTINED = 0x00000001;
/** @hide */
@@ -9751,7 +9756,10 @@
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
@RequiresPermission(value=Manifest.permission.SUSPEND_APPS, conditional=true)
+ @SuppressLint("NullableCollection")
@Nullable
public String[] setPackagesSuspended(@Nullable String[] packageNames, boolean suspended,
@Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras,
@@ -9958,16 +9966,6 @@
public abstract @NonNull PackageInstaller getPackageInstaller();
/**
- * {@link PackageArchiver} can be used to archive and restore archived packages.
- *
- * @hide
- */
- @SystemApi
- public @NonNull PackageArchiver getPackageArchiver() {
- throw new UnsupportedOperationException(
- "getPackageArchiver not implemented in subclass");
- }
- /**
* Adds a {@code CrossProfileIntentFilter}. After calling this method all
* intents sent from the user with id sourceUserId can also be be resolved
* by activities in the user with id targetUserId if they match the
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 41ba1dc..4b579e7 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1445,7 +1445,7 @@
verified.getPublicKeys(),
verified.getPastSigningCertificates());
} else {
- if (!Signature.areExactMatch(pkg.mSigningDetails.signatures,
+ if (!Signature.areExactArraysMatch(pkg.mSigningDetails.signatures,
verified.getSignatures())) {
throw new PackageParserException(
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
@@ -6468,7 +6468,7 @@
}
}
} else {
- return Signature.areEffectiveMatch(oldDetails.signatures, signatures);
+ return Signature.areEffectiveArraysMatch(oldDetails.signatures, signatures);
}
return false;
}
@@ -6616,7 +6616,7 @@
/** Returns true if the signatures in this and other match exactly. */
public boolean signaturesMatchExactly(SigningDetails other) {
- return Signature.areExactMatch(this.signatures, other.signatures);
+ return Signature.areExactArraysMatch(this.signatures, other.signatures);
}
@Override
@@ -6668,7 +6668,7 @@
SigningDetails that = (SigningDetails) o;
if (signatureSchemeVersion != that.signatureSchemeVersion) return false;
- if (!Signature.areExactMatch(signatures, that.signatures)) return false;
+ if (!Signature.areExactArraysMatch(signatures, that.signatures)) return false;
if (publicKeys != null) {
if (!publicKeys.equals((that.publicKeys))) {
return false;
@@ -6677,7 +6677,8 @@
return false;
}
- // can't use Signature.areExactMatch() because order matters with the past signing certs
+ // can't use Signature.areExactArraysMatch() because order matters with the past
+ // signing certs
if (!Arrays.equals(pastSigningCertificates, that.pastSigningCertificates)) {
return false;
}
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index b049880..a69eee7 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -307,11 +307,27 @@
}
/**
- * Test if given {@link Signature} sets are exactly equal.
- *
+ * Test if given {@link SigningDetails} are exactly equal.
* @hide
*/
- public static boolean areExactMatch(Signature[] a, Signature[] b) {
+ public static boolean areExactMatch(SigningDetails ad, SigningDetails bd) {
+ return areExactArraysMatch(ad.getSignatures(), bd.getSignatures());
+ }
+
+ /**
+ * Test if given {@link SigningDetails} and {@link Signature} set are exactly equal.
+ * @hide
+ */
+ public static boolean areExactMatch(SigningDetails ad, Signature[] b) {
+ return areExactArraysMatch(ad.getSignatures(), b);
+ }
+
+
+ /**
+ * Test if given {@link Signature} sets are exactly equal.
+ * @hide
+ */
+ static boolean areExactArraysMatch(Signature[] a, Signature[] b) {
return (ArrayUtils.size(a) == ArrayUtils.size(b)) && ArrayUtils.containsAll(a, b)
&& ArrayUtils.containsAll(b, a);
}
@@ -329,7 +345,12 @@
* substantially, usually a signal of something fishy going on.
* @hide
*/
- public static boolean areEffectiveMatch(Signature[] a, Signature[] b)
+ public static boolean areEffectiveMatch(SigningDetails a, SigningDetails b)
+ throws CertificateException {
+ return areEffectiveArraysMatch(a.getSignatures(), b.getSignatures());
+ }
+
+ static boolean areEffectiveArraysMatch(Signature[] a, Signature[] b)
throws CertificateException {
final CertificateFactory cf = CertificateFactory.getInstance("X.509");
@@ -342,7 +363,7 @@
bPrime[i] = bounce(cf, b[i]);
}
- return areExactMatch(aPrime, bPrime);
+ return areExactArraysMatch(aPrime, bPrime);
}
/**
diff --git a/core/java/android/content/pm/SigningDetails.java b/core/java/android/content/pm/SigningDetails.java
index af2649f..8c21974 100644
--- a/core/java/android/content/pm/SigningDetails.java
+++ b/core/java/android/content/pm/SigningDetails.java
@@ -656,7 +656,7 @@
}
}
} else {
- return Signature.areEffectiveMatch(oldDetails.mSignatures, mSignatures);
+ return Signature.areEffectiveMatch(oldDetails, this);
}
return false;
}
@@ -800,7 +800,7 @@
/** Returns true if the signatures in this and other match exactly. */
public boolean signaturesMatchExactly(@NonNull SigningDetails other) {
- return Signature.areExactMatch(mSignatures, other.mSignatures);
+ return Signature.areExactMatch(this, other);
}
@Override
@@ -853,7 +853,7 @@
final SigningDetails that = (SigningDetails) o;
if (mSignatureSchemeVersion != that.mSignatureSchemeVersion) return false;
- if (!Signature.areExactMatch(mSignatures, that.mSignatures)) return false;
+ if (!Signature.areExactMatch(this, that)) return false;
if (mPublicKeys != null) {
if (!mPublicKeys.equals((that.mPublicKeys))) {
return false;
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
new file mode 100644
index 0000000..0c8eb02
--- /dev/null
+++ b/core/java/android/content/pm/flags.aconfig
@@ -0,0 +1,15 @@
+package: "android.content.pm"
+
+flag {
+ name: "quarantined_enabled"
+ namespace: "package_manager_service"
+ description: "Feature flag for Quarantined state"
+ bug: "269127435"
+}
+
+flag {
+ name: "archiving"
+ namespace: "package_manager_service"
+ description: "Feature flag to enable the archiving feature."
+ bug: "278553670"
+}
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index 3e1c5bb..153dd9a 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -253,8 +253,8 @@
if (existingSigningDetails == SigningDetails.UNKNOWN) {
return verified;
} else {
- if (!Signature.areExactMatch(existingSigningDetails.getSignatures(),
- verified.getResult().getSignatures())) {
+ if (!Signature.areExactMatch(existingSigningDetails,
+ verified.getResult())) {
return input.error(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
baseCodePath + " has mismatched certificates");
}
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 08ba5b6..f929c1f 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -100,7 +100,7 @@
* The effective screen density we have selected for this application.
*/
public final int applicationDensity;
-
+
/**
* Application's scale.
*/
@@ -112,9 +112,27 @@
*/
public final float applicationInvertedScale;
+ /**
+ * Application's density scale.
+ *
+ * <p>In most cases this is equal to {@link #applicationScale}, but in some cases e.g.
+ * Automotive the requirement is to just scale the density and keep the resolution the same.
+ * This is used for artificially making apps look zoomed in to compensate for the user distance
+ * from the screen.
+ */
+ public final float applicationDensityScale;
+
+ /**
+ * Application's density inverted scale.
+ */
+ public final float applicationDensityInvertedScale;
+
/** The process level override inverted scale. See {@link #HAS_OVERRIDE_SCALING}. */
private static float sOverrideInvertedScale = 1f;
+ /** The process level override inverted density scale. See {@link #HAS_OVERRIDE_SCALING}. */
+ private static float sOverrideDensityInvertScale = 1f;
+
@UnsupportedAppUsage
@Deprecated
public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
@@ -123,17 +141,24 @@
}
public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
- boolean forceCompat, float overrideScale) {
+ boolean forceCompat, float scaleFactor) {
+ this(appInfo, screenLayout, sw, forceCompat, scaleFactor, scaleFactor);
+ }
+
+ public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
+ boolean forceCompat, float scaleFactor, float densityScaleFactor) {
int compatFlags = 0;
if (appInfo.targetSdkVersion < VERSION_CODES.O) {
compatFlags |= NEEDS_COMPAT_RES;
}
- if (overrideScale != 1.0f) {
- applicationScale = overrideScale;
- applicationInvertedScale = 1.0f / overrideScale;
+ if (scaleFactor != 1f || densityScaleFactor != 1f) {
+ applicationScale = scaleFactor;
+ applicationInvertedScale = 1f / scaleFactor;
+ applicationDensityScale = densityScaleFactor;
+ applicationDensityInvertedScale = 1f / densityScaleFactor;
applicationDensity = (int) ((DisplayMetrics.DENSITY_DEVICE_STABLE
- * applicationInvertedScale) + .5f);
+ * applicationDensityInvertedScale) + .5f);
mCompatibilityFlags = NEVER_NEEDS_COMPAT | HAS_OVERRIDE_SCALING;
// Override scale has the highest priority. So ignore other compatibility attributes.
return;
@@ -181,7 +206,8 @@
applicationDensity = DisplayMetrics.DENSITY_DEVICE;
applicationScale = 1.0f;
applicationInvertedScale = 1.0f;
-
+ applicationDensityScale = 1.0f;
+ applicationDensityInvertedScale = 1.0f;
} else {
/**
* Has the application said that its UI is expandable? Based on the
@@ -271,11 +297,16 @@
applicationDensity = DisplayMetrics.DENSITY_DEVICE;
applicationScale = 1.0f;
applicationInvertedScale = 1.0f;
+ applicationDensityScale = 1.0f;
+ applicationDensityInvertedScale = 1.0f;
} else {
applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
applicationScale = DisplayMetrics.DENSITY_DEVICE
/ (float) DisplayMetrics.DENSITY_DEFAULT;
applicationInvertedScale = 1.0f / applicationScale;
+ applicationDensityScale = DisplayMetrics.DENSITY_DEVICE
+ / (float) DisplayMetrics.DENSITY_DEFAULT;
+ applicationDensityInvertedScale = 1f / applicationDensityScale;
compatFlags |= SCALING_REQUIRED;
}
}
@@ -289,6 +320,8 @@
applicationDensity = dens;
applicationScale = scale;
applicationInvertedScale = invertedScale;
+ applicationDensityScale = (float) DisplayMetrics.DENSITY_DEVICE_STABLE / dens;
+ applicationDensityInvertedScale = 1f / applicationDensityScale;
}
@UnsupportedAppUsage
@@ -528,7 +561,8 @@
/** Applies the compatibility adjustment to the display metrics. */
public void applyDisplayMetricsIfNeeded(DisplayMetrics inoutDm, boolean applyToSize) {
if (hasOverrideScale()) {
- scaleDisplayMetrics(sOverrideInvertedScale, inoutDm, applyToSize);
+ scaleDisplayMetrics(sOverrideInvertedScale, sOverrideDensityInvertScale, inoutDm,
+ applyToSize);
return;
}
if (!equals(DEFAULT_COMPATIBILITY_INFO)) {
@@ -548,15 +582,17 @@
}
if (isScalingRequired()) {
- scaleDisplayMetrics(applicationInvertedScale, inoutDm, true /* applyToSize */);
+ scaleDisplayMetrics(applicationInvertedScale, applicationDensityInvertedScale, inoutDm,
+ true /* applyToSize */);
}
}
/** Scales the density of the given display metrics. */
- private static void scaleDisplayMetrics(float invertedRatio, DisplayMetrics inoutDm,
- boolean applyToSize) {
- inoutDm.density = inoutDm.noncompatDensity * invertedRatio;
- inoutDm.densityDpi = (int) ((inoutDm.noncompatDensityDpi * invertedRatio) + .5f);
+ private static void scaleDisplayMetrics(float invertScale, float densityInvertScale,
+ DisplayMetrics inoutDm, boolean applyToSize) {
+ inoutDm.density = inoutDm.noncompatDensity * densityInvertScale;
+ inoutDm.densityDpi = (int) ((inoutDm.noncompatDensityDpi
+ * densityInvertScale) + .5f);
// Note: since this is changing the scaledDensity, you might think we also need to change
// inoutDm.fontScaleConverter to accurately calculate non-linear font scaling. But we're not
// going to do that, for a couple of reasons (see b/265695259 for details):
@@ -570,12 +606,12 @@
// b. Sometime later by WindowManager in onResume or other windowing events. In this case
// the DisplayMetrics object is never used by the app/resources, so it's ok if
// fontScaleConverter is null because it's not being used to scale fonts anyway.
- inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * invertedRatio;
- inoutDm.xdpi = inoutDm.noncompatXdpi * invertedRatio;
- inoutDm.ydpi = inoutDm.noncompatYdpi * invertedRatio;
+ inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * densityInvertScale;
+ inoutDm.xdpi = inoutDm.noncompatXdpi * densityInvertScale;
+ inoutDm.ydpi = inoutDm.noncompatYdpi * densityInvertScale;
if (applyToSize) {
- inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f);
- inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f);
+ inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertScale + 0.5f);
+ inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertScale + 0.5f);
}
}
@@ -594,38 +630,55 @@
}
inoutConfig.densityDpi = displayDensity;
if (isScalingRequired()) {
- scaleConfiguration(applicationInvertedScale, inoutConfig);
+ scaleConfiguration(applicationInvertedScale, applicationDensityInvertedScale,
+ inoutConfig);
}
}
/** Scales the density and bounds of the given configuration. */
- public static void scaleConfiguration(float invertedRatio, Configuration inoutConfig) {
- inoutConfig.densityDpi = (int) ((inoutConfig.densityDpi * invertedRatio) + .5f);
- inoutConfig.windowConfiguration.scale(invertedRatio);
+ public static void scaleConfiguration(float invertScale, Configuration inoutConfig) {
+ scaleConfiguration(invertScale, invertScale, inoutConfig);
+ }
+
+ /** Scales the density and bounds of the given configuration. */
+ public static void scaleConfiguration(float invertScale, float densityInvertScale,
+ Configuration inoutConfig) {
+ inoutConfig.densityDpi = (int) ((inoutConfig.densityDpi
+ * densityInvertScale) + .5f);
+ inoutConfig.windowConfiguration.scale(invertScale);
}
/** @see #sOverrideInvertedScale */
public static void applyOverrideScaleIfNeeded(Configuration config) {
if (!hasOverrideScale()) return;
- scaleConfiguration(sOverrideInvertedScale, config);
+ scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale, config);
}
/** @see #sOverrideInvertedScale */
public static void applyOverrideScaleIfNeeded(MergedConfiguration mergedConfig) {
if (!hasOverrideScale()) return;
- scaleConfiguration(sOverrideInvertedScale, mergedConfig.getGlobalConfiguration());
- scaleConfiguration(sOverrideInvertedScale, mergedConfig.getOverrideConfiguration());
- scaleConfiguration(sOverrideInvertedScale, mergedConfig.getMergedConfiguration());
+ scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
+ mergedConfig.getGlobalConfiguration());
+ scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
+ mergedConfig.getOverrideConfiguration());
+ scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
+ mergedConfig.getMergedConfiguration());
}
/** Returns {@code true} if this process is in a environment with override scale. */
private static boolean hasOverrideScale() {
- return sOverrideInvertedScale != 1f;
+ return sOverrideInvertedScale != 1f || sOverrideDensityInvertScale != 1f;
}
/** @see #sOverrideInvertedScale */
- public static void setOverrideInvertedScale(float invertedRatio) {
- sOverrideInvertedScale = invertedRatio;
+ public static void setOverrideInvertedScale(float invertScale) {
+ setOverrideInvertedScale(invertScale, invertScale);
+ }
+
+ /** @see #sOverrideInvertedScale */
+ public static void setOverrideInvertedScale(float invertScale, float densityInvertScale) {
+ sOverrideInvertedScale = invertScale;
+ sOverrideDensityInvertScale = densityInvertScale;
}
/** @see #sOverrideInvertedScale */
@@ -633,6 +686,11 @@
return sOverrideInvertedScale;
}
+ /** @see #sOverrideDensityInvertScale */
+ public static float getOverrideDensityInvertedScale() {
+ return sOverrideDensityInvertScale;
+ }
+
/**
* Compute the frame Rect for applications runs under compatibility mode.
*
@@ -693,6 +751,8 @@
if (applicationDensity != oc.applicationDensity) return false;
if (applicationScale != oc.applicationScale) return false;
if (applicationInvertedScale != oc.applicationInvertedScale) return false;
+ if (applicationDensityScale != oc.applicationDensityScale) return false;
+ if (applicationDensityInvertedScale != oc.applicationDensityInvertedScale) return false;
return true;
} catch (ClassCastException e) {
return false;
@@ -713,6 +773,8 @@
if (hasOverrideScaling()) {
sb.append(" overrideInvScale=");
sb.append(applicationInvertedScale);
+ sb.append(" overrideDensityInvScale=");
+ sb.append(applicationDensityInvertedScale);
}
if (!supportsScreen()) {
sb.append(" resizing");
@@ -734,6 +796,8 @@
result = 31 * result + applicationDensity;
result = 31 * result + Float.floatToIntBits(applicationScale);
result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
+ result = 31 * result + Float.floatToIntBits(applicationDensityScale);
+ result = 31 * result + Float.floatToIntBits(applicationDensityInvertedScale);
return result;
}
@@ -748,6 +812,8 @@
dest.writeInt(applicationDensity);
dest.writeFloat(applicationScale);
dest.writeFloat(applicationInvertedScale);
+ dest.writeFloat(applicationDensityScale);
+ dest.writeFloat(applicationDensityInvertedScale);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -769,5 +835,61 @@
applicationDensity = source.readInt();
applicationScale = source.readFloat();
applicationInvertedScale = source.readFloat();
+ applicationDensityScale = source.readFloat();
+ applicationDensityInvertedScale = source.readFloat();
+ }
+
+ /**
+ * A data class for holding scale factor for width, height, and density.
+ */
+ public static final class CompatScale {
+
+ public final float mScaleFactor;
+ public final float mDensityScaleFactor;
+
+ public CompatScale(float scaleFactor) {
+ this(scaleFactor, scaleFactor);
+ }
+
+ public CompatScale(float scaleFactor, float densityScaleFactor) {
+ mScaleFactor = scaleFactor;
+ mDensityScaleFactor = densityScaleFactor;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof CompatScale)) {
+ return false;
+ }
+ try {
+ CompatScale oc = (CompatScale) o;
+ if (mScaleFactor != oc.mScaleFactor) return false;
+ if (mDensityScaleFactor != oc.mDensityScaleFactor) return false;
+ return true;
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("mScaleFactor= ");
+ sb.append(mScaleFactor);
+ sb.append(" mDensityScaleFactor= ");
+ sb.append(mDensityScaleFactor);
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Float.floatToIntBits(mScaleFactor);
+ result = 31 * result + Float.floatToIntBits(mDensityScaleFactor);
+ return result;
+ }
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b962d76..7e71134f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10664,6 +10664,14 @@
"search_press_hold_nav_handle_enabled";
/**
+ * Whether long-pressing on the home button can trigger search.
+ *
+ * @hide
+ */
+ public static final String SEARCH_LONG_PRESS_HOME_ENABLED =
+ "search_long_press_home_enabled";
+
+ /**
* Control whether Night display is currently activated.
* @hide
*/
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index b27dac2..b6c2b83 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -6,3 +6,10 @@
description: "Feature flag for fs-verity API"
bug: "285185747"
}
+
+flag {
+ name: "fix_unlocked_device_required_keys"
+ namespace: "hardware_backed_security"
+ description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
+ bug: "296464083"
+}
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 14fc585..65a1da6 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -16,6 +16,9 @@
package android.text;
+import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -55,9 +58,7 @@
* line width
* @param includePad set whether to include extra space beyond font ascent and descent which is
* needed to avoid clipping in some scripts
- * @deprecated Use {@link android.text.Layout.Builder} instead.
*/
- @Deprecated
public static BoringLayout make(CharSequence source, TextPaint paint, int outerWidth,
Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics,
boolean includePad) {
@@ -83,9 +84,7 @@
* @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
* {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
* not used, {@code outerWidth} is used instead
- * @deprecated Use {@link android.text.Layout.Builder} instead.
*/
- @Deprecated
public static BoringLayout make(CharSequence source, TextPaint paint, int outerWidth,
Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics,
boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
@@ -117,9 +116,7 @@
* False for keeping the first font's line height. If some glyphs
* requires larger vertical spaces, by passing true to this
* argument, the layout increase the line height to fit all glyphs.
- * @deprecated Use {@link android.text.Layout.Builder} instead.
*/
- @Deprecated
public static @NonNull BoringLayout make(
@NonNull CharSequence source, @NonNull TextPaint paint,
@IntRange(from = 0) int outerWidth,
@@ -266,9 +263,7 @@
* line width
* @param includePad set whether to include extra space beyond font ascent and descent which is
* needed to avoid clipping in some scripts
- * @deprecated Use {@link android.text.Layout.Builder} instead.
*/
- @Deprecated
public BoringLayout(CharSequence source, TextPaint paint, int outerwidth, Alignment align,
float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad) {
super(source, paint, outerwidth, align, TextDirectionHeuristics.LTR, spacingMult,
@@ -302,9 +297,7 @@
* @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
* {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
* not used, {@code outerWidth} is used instead
- * @deprecated Use {@link android.text.Layout.Builder} instead.
*/
- @Deprecated
public BoringLayout(CharSequence source, TextPaint paint, int outerWidth, Alignment align,
float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad,
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
@@ -333,9 +326,7 @@
* False for keeping the first font's line height. If some glyphs
* requires larger vertical spaces, by passing true to this
* argument, the layout increase the line height to fit all glyphs.
- * @deprecated Use {@link android.text.Layout.Builder} instead.
*/
- @Deprecated
public BoringLayout(
@NonNull CharSequence source, @NonNull TextPaint paint,
@IntRange(from = 0) int outerWidth, @NonNull Alignment align, float spacingMult,
@@ -478,9 +469,7 @@
* @param paint a paint
* @return layout metric for the given text. null if given text is unable to be handled by
* BoringLayout.
- * @deprecated Use {@link android.text.Layout.Builder} instead.
*/
- @Deprecated
public static Metrics isBoring(CharSequence text, TextPaint paint) {
return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, null);
}
@@ -495,9 +484,7 @@
* @return layout metric for the given text. If metrics is not null, this method fills values
* to given metrics object instead of allocating new metrics object. null if given text
* is unable to be handled by BoringLayout.
- * @deprecated Use {@link android.text.Layout.Builder} instead.
*/
- @Deprecated
public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) {
return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, metrics);
}
@@ -557,9 +544,7 @@
* argument, the layout increase the line height to fit all glyphs.
* @param metrics the out metrics.
* @return metrics on success. null if text cannot be rendered by BoringLayout.
- * @deprecated Use {@link android.text.Layout.Builder} instead.
*/
- @Deprecated
public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
@NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
@Nullable Metrics metrics) {
@@ -746,6 +731,7 @@
*
* @return a drawing bounding box.
*/
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
@NonNull public RectF getDrawingBoundingBox() {
return mDrawingBounds;
}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 469e166..4f4dea7 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -16,6 +16,9 @@
package android.text;
+import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -1010,6 +1013,7 @@
* @return bounding rectangle
*/
@NonNull
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public RectF computeDrawingBoundingBox() {
float left = 0;
float right = 0;
@@ -3436,6 +3440,7 @@
*
* @see StaticLayout.Builder
*/
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public static final class Builder {
/**
* Construct a builder class.
@@ -3776,6 +3781,7 @@
// The corresponding getter is getUseBoundsForWidth
@NonNull
@SuppressLint("MissingGetterMatchingBuilder")
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
mUseBoundsForWidth = useBoundsForWidth;
return this;
@@ -3865,6 +3871,7 @@
* @see Layout.Builder
*/
@NonNull
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public final CharSequence getText() {
return mText;
}
@@ -3914,6 +3921,7 @@
* @see StaticLayout.Builder#setTextDirection(TextDirectionHeuristic)
*/
@NonNull
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public final TextDirectionHeuristic getTextDirectionHeuristic() {
return mTextDir;
}
@@ -3940,6 +3948,7 @@
* @see StaticLayout.Builder#setLineSpacing(float, float)
* @see Layout#getSpacingMultiplier()
*/
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public final float getLineSpacingMultiplier() {
return mSpacingMult;
}
@@ -3966,6 +3975,7 @@
* @see StaticLayout.Builder#setLineSpacing(float, float)
* @see Layout#getSpacingAdd()
*/
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public final float getLineSpacingAmount() {
return mSpacingAdd;
}
@@ -3977,6 +3987,7 @@
* @see Layout.Builder#setFontPaddingIncluded(boolean)
* @see StaticLayout.Builder#setIncludePad(boolean)
*/
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public final boolean isFontPaddingIncluded() {
return mIncludePad;
}
@@ -4024,6 +4035,7 @@
* @see Layout#getEllipsizedWidth()
*/
@Nullable
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public final TextUtils.TruncateAt getEllipsize() {
return mEllipsize;
}
@@ -4039,6 +4051,7 @@
* @see StaticLayout.Builder#setMaxLines(int)
*/
@IntRange(from = 1)
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public final int getMaxLines() {
return mMaxLines;
}
@@ -4051,6 +4064,7 @@
* @see StaticLayout.Builder#setBreakStrategy(int)
*/
@BreakStrategy
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public final int getBreakStrategy() {
return mBreakStrategy;
}
@@ -4063,6 +4077,7 @@
* @see StaticLayout.Builder#setHyphenationFrequency(int)
*/
@HyphenationFrequency
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public final int getHyphenationFrequency() {
return mHyphenationFrequency;
}
@@ -4078,6 +4093,7 @@
* @see StaticLayout.Builder#setIndents(int[], int[])
*/
@Nullable
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public final int[] getLeftIndents() {
if (mLeftIndents == null) {
return null;
@@ -4098,6 +4114,7 @@
* @see StaticLayout.Builder#setIndents(int[], int[])
*/
@Nullable
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public final int[] getRightIndents() {
if (mRightIndents == null) {
return null;
@@ -4115,6 +4132,7 @@
* @see StaticLayout.Builder#setJustificationMode(int)
*/
@JustificationMode
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public final int getJustificationMode() {
return mJustificationMode;
}
@@ -4128,6 +4146,7 @@
*/
// not being final because of subclass has already published API.
@NonNull
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public LineBreakConfig getLineBreakConfig() {
return mLineBreakConfig;
}
@@ -4141,6 +4160,7 @@
* @see StaticLayout.Builder#setUseBoundsForWidth(boolean)
* @see DynamicLayout.Builder#setUseBoundsForWidth(boolean)
*/
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public boolean getUseBoundsForWidth() {
return mUseBoundsForWidth;
}
diff --git a/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig b/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig
new file mode 100644
index 0000000..60f1e88
--- /dev/null
+++ b/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.text.flags"
+
+flag {
+ name: "no_break_no_hyphenation_span"
+ namespace: "text"
+ description: "A feature flag that adding new spans that prevents line breaking and hyphenation."
+ bug: "283193586"
+}
diff --git a/core/java/android/text/style/LineBreakConfigSpan.java b/core/java/android/text/style/LineBreakConfigSpan.java
index 90a79c6..b8033a9 100644
--- a/core/java/android/text/style/LineBreakConfigSpan.java
+++ b/core/java/android/text/style/LineBreakConfigSpan.java
@@ -16,6 +16,9 @@
package android.text.style;
+import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.graphics.text.LineBreakConfig;
@@ -24,6 +27,7 @@
/**
* LineBreakSpan for changing line break style of the specific region of the text.
*/
+@FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
public class LineBreakConfigSpan {
private final LineBreakConfig mLineBreakConfig;
@@ -60,4 +64,22 @@
public String toString() {
return "LineBreakConfigSpan{mLineBreakConfig=" + mLineBreakConfig + '}';
}
+
+ private static final LineBreakConfig sNoHyphenationConfig = new LineBreakConfig.Builder()
+ .setHyphenation(LineBreakConfig.HYPHENATION_DISABLED)
+ .build();
+
+ /**
+ * A specialized {@link LineBreakConfigSpan} that used for preventing hyphenation.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public static final class NoHyphenationSpan extends LineBreakConfigSpan {
+ /**
+ * Construct a new {@link NoHyphenationSpan}.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public NoHyphenationSpan() {
+ super(sNoHyphenationConfig);
+ }
+ }
}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 12527e9..0ed275c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -208,6 +208,14 @@
public static final String SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION =
"settings_remote_device_credential_validation";
+ // TODO(b/295516544): Remove this when trunk stable feature flag is available.
+ /**
+ * Flag to enable / disable the Private Space Settings. It's disabled by default.
+ * @hide
+ */
+ public static final String SETTINGS_PRIVATE_SPACE_SETTINGS =
+ "settings_private_space_settings";
+
private static final Map<String, String> DEFAULT_FLAGS;
@@ -256,6 +264,7 @@
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
// TODO: b/298454866 Replace with Trunk Stable Feature Flag
DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false");
+ DEFAULT_FLAGS.put(SETTINGS_PRIVATE_SPACE_SETTINGS, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index d2a18dd..a6724da 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -48,6 +48,7 @@
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -428,7 +429,7 @@
// make sure all entries use the same signing certs
final Signature[] entrySigs = convertToSignatures(entryCerts);
- if (!Signature.areExactMatch(lastSigs, entrySigs)) {
+ if (!Arrays.equals(lastSigs, entrySigs)) {
return input.error(
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
"Package " + apkPath + " has mismatched certificates at entry "
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index b8385c6..e64274e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3139,15 +3139,6 @@
public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 1 << 10;
/**
- * Flag to force the status bar window to be visible all the time. If the bar is hidden when
- * this flag is set it will be shown again.
- * This can only be set by {@link LayoutParams#TYPE_STATUS_BAR}.
- *
- * {@hide}
- */
- public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 1 << 11;
-
- /**
* Flag to indicate that the window frame should be the requested frame adding the display
* cutout frame. This will only be applied if a specific size smaller than the parent frame
* is given, and the window is covering the display cutout. The extended frame will not be
@@ -3238,15 +3229,6 @@
public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 1 << 22;
/**
- * Flag to indicate that the status bar window is in a state such that it forces showing
- * the navigation bar unless the navigation bar window is explicitly set to
- * {@link View#GONE}.
- * It only takes effects if this is set by {@link LayoutParams#TYPE_STATUS_BAR}.
- * @hide
- */
- public static final int PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION = 1 << 23;
-
- /**
* Flag to indicate that the window is color space agnostic, and the color can be
* interpreted to any color space.
* @hide
@@ -3334,7 +3316,6 @@
PRIVATE_FLAG_SYSTEM_ERROR,
PRIVATE_FLAG_OPTIMIZE_MEASURE,
PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
- PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
@@ -3345,7 +3326,6 @@
PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
PRIVATE_FLAG_NOT_MAGNIFIABLE,
- PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
PRIVATE_FLAG_USE_BLAST,
PRIVATE_FLAG_APPEARANCE_CONTROLLED,
@@ -3401,10 +3381,6 @@
equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
@ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
- equals = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
- name = "FORCE_STATUS_BAR_VISIBLE"),
- @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
equals = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
name = "LAYOUT_SIZE_EXTENDED_BY_CUTOUT"),
@@ -3445,10 +3421,6 @@
equals = PRIVATE_FLAG_NOT_MAGNIFIABLE,
name = "NOT_MAGNIFIABLE"),
@ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
- equals = PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
- name = "STATUS_FORCE_SHOW_NAVIGATION"),
- @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
name = "COLOR_SPACE_AGNOSTIC"),
@@ -4412,6 +4384,16 @@
public InsetsFrameProvider[] providedInsets;
/**
+ * Specifies which {@link InsetsType}s should be forcibly shown. The types shown by this
+ * method won't affect the app's layout. This field only takes effects if the caller has
+ * {@link android.Manifest.permission#STATUS_BAR_SERVICE} or the caller has the same uid as
+ * the recents component.
+ *
+ * @hide
+ */
+ public @InsetsType int forciblyShownTypes;
+
+ /**
* {@link LayoutParams} to be applied to the window when layout with a assigned rotation.
* This will make layout during rotation change smoothly.
*
@@ -4869,6 +4851,7 @@
out.writeInt(mBlurBehindRadius);
out.writeBoolean(mWallpaperTouchEventsEnabled);
out.writeTypedArray(providedInsets, 0 /* parcelableFlags */);
+ out.writeInt(forciblyShownTypes);
checkNonRecursiveParams();
out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
out.writeInt(mDisplayFlags);
@@ -4940,6 +4923,7 @@
mBlurBehindRadius = in.readInt();
mWallpaperTouchEventsEnabled = in.readBoolean();
providedInsets = in.createTypedArray(InsetsFrameProvider.CREATOR);
+ forciblyShownTypes = in.readInt();
paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
mDisplayFlags = in.readInt();
}
@@ -5245,6 +5229,11 @@
changes |= LAYOUT_CHANGED;
}
+ if (forciblyShownTypes != o.forciblyShownTypes) {
+ forciblyShownTypes = o.forciblyShownTypes;
+ changes |= PRIVATE_FLAGS_CHANGED;
+ }
+
if (paramsForRotation != o.paramsForRotation) {
if ((changes & LAYOUT_CHANGED) == 0) {
if (paramsForRotation != null && o.paramsForRotation != null
@@ -5482,6 +5471,11 @@
sb.append(prefix).append(" ").append(providedInsets[i]);
}
}
+ if (forciblyShownTypes != 0) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" forciblyShownTypes=").append(
+ WindowInsets.Type.toString(forciblyShownTypes));
+ }
if (paramsForRotation != null && paramsForRotation.length != 0) {
sb.append(System.lineSeparator());
sb.append(prefix).append(" paramsForRotation:");
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f6b337c..59344b0 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -27,12 +27,14 @@
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import android.R;
import android.annotation.CallSuper;
import android.annotation.CheckResult;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -238,6 +240,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastMath;
import com.android.internal.util.Preconditions;
+import com.android.text.flags.Flags;
import libcore.util.EmptyArray;
@@ -523,6 +526,15 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.P)
public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id
+
+ /**
+ * This change ID enables the bounding box based layout.
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+ public static final long USE_BOUNDS_FOR_WIDTH = 63938206; // buganizer id
+
// System wide time for last cut, copy or text changed action.
static long sLastCutCopyOrTextChangedTime;
@@ -1621,7 +1633,11 @@
mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE;
}
- mUseBoundsForWidth = false; // TODO: Make enable this by default.
+ if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) {
+ mUseBoundsForWidth = Flags.useBoundsForWidth();
+ } else {
+ mUseBoundsForWidth = false;
+ }
// TODO(b/179693024): Use a ChangeId instead.
mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R;
@@ -4848,6 +4864,7 @@
* width.
* @see #getUseBoundsForWidth()
*/
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public void setUseBoundsForWidth(boolean useBoundsForWidth) {
if (mUseBoundsForWidth != useBoundsForWidth) {
mUseBoundsForWidth = useBoundsForWidth;
@@ -4865,6 +4882,7 @@
* @see #setUseBoundsForWidth(boolean)
* @return True if using bounding box for width, false if using advance for width.
*/
+ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public boolean getUseBoundsForWidth() {
return mUseBoundsForWidth;
}
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index 89271b5..6884e63 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -89,9 +89,10 @@
return view;
}
+ private final WeakReference<Context> mContext;
private final Resources mResources;
private final WeakReference<WindowManager> mWindowManager;
- private final WeakReference<AccessibilityManager> mAccessibilityManager;
+ private final IAccessibilityManager mAccessibilityManagerService;
private final INotificationManager mNotificationManager;
private final String mPackageName;
private final String mContextPackageName;
@@ -101,21 +102,14 @@
public ToastPresenter(Context context, IAccessibilityManager accessibilityManager,
INotificationManager notificationManager, String packageName) {
+ mContext = new WeakReference<>(context);
mResources = context.getResources();
mWindowManager = new WeakReference<>(context.getSystemService(WindowManager.class));
mNotificationManager = notificationManager;
mPackageName = packageName;
mContextPackageName = context.getPackageName();
mParams = createLayoutParams();
-
- // We obtain AccessibilityManager manually via its constructor instead of using method
- // AccessibilityManager.getInstance() for 2 reasons:
- // 1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
- // 2. getInstance() caches the instance for the process even if we pass a different
- // context to it. This is problematic for multi-user because callers can pass a context
- // created via Context.createContextAsUser().
- mAccessibilityManager = new WeakReference<>(
- new AccessibilityManager(context, accessibilityManager, context.getUserId()));
+ mAccessibilityManagerService = accessibilityManager;
}
public String getPackageName() {
@@ -306,11 +300,20 @@
* enabled.
*/
public void trySendAccessibilityEvent(View view, String packageName) {
- final AccessibilityManager accessibilityManager = mAccessibilityManager.get();
- if (accessibilityManager == null) {
+ final Context context = mContext.get();
+ if (context == null) {
return;
}
+ // We obtain AccessibilityManager manually via its constructor instead of using method
+ // AccessibilityManager.getInstance() for 2 reasons:
+ // 1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
+ // 2. getInstance() caches the instance for the process even if we pass a different
+ // context to it. This is problematic for multi-user because callers can pass a context
+ // created via Context.createContextAsUser().
+ final AccessibilityManager accessibilityManager = new AccessibilityManager(context,
+ mAccessibilityManagerService, context.getUserId());
+
if (!accessibilityManager.isEnabled()) {
accessibilityManager.removeClient();
return;
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index 5e2eceb..dee4935 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -177,7 +177,7 @@
* <code>1</code> would return the work profile {@link ProfileDescriptor}.</li>
* </ul>
*/
- abstract ProfileDescriptor getItem(int pageIndex);
+ public abstract ProfileDescriptor getItem(int pageIndex);
/**
* Returns the number of {@link ProfileDescriptor} objects.
@@ -438,8 +438,8 @@
&& isQuietModeEnabled(mWorkProfileUserHandle));
}
- protected class ProfileDescriptor {
- final ViewGroup rootView;
+ public static class ProfileDescriptor {
+ public final ViewGroup rootView;
private final ViewGroup mEmptyStateView;
ProfileDescriptor(ViewGroup rootView) {
this.rootView = rootView;
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index 7beb059..8197e26 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -94,7 +94,7 @@
}
@Override
- ChooserProfileDescriptor getItem(int pageIndex) {
+ public ChooserProfileDescriptor getItem(int pageIndex) {
return mItems[pageIndex];
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index ac15f11..7534d29 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -2623,13 +2623,13 @@
* An a11y delegate that expands resolver drawer when gesture navigation reaches a partially
* invisible target in the list.
*/
- private static class AppListAccessibilityDelegate extends View.AccessibilityDelegate {
+ public static class AppListAccessibilityDelegate extends View.AccessibilityDelegate {
private final ResolverDrawerLayout mDrawer;
@Nullable
private final View mBottomBar;
private final Rect mRect = new Rect();
- private AppListAccessibilityDelegate(ResolverDrawerLayout drawer) {
+ public AppListAccessibilityDelegate(ResolverDrawerLayout drawer) {
mDrawer = drawer;
mBottomBar = mDrawer.findViewById(R.id.button_bar_container);
}
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index 7677912..031f9d3 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -79,7 +79,7 @@
}
@Override
- ResolverProfileDescriptor getItem(int pageIndex) {
+ public ResolverProfileDescriptor getItem(int pageIndex) {
return mItems[pageIndex];
}
diff --git a/core/java/com/android/internal/util/FileRotator.java b/core/java/com/android/internal/util/FileRotator.java
index 5bc48c5..c9d9926 100644
--- a/core/java/com/android/internal/util/FileRotator.java
+++ b/core/java/com/android/internal/util/FileRotator.java
@@ -19,6 +19,9 @@
import android.annotation.NonNull;
import android.os.FileUtils;
import android.util.Log;
+import android.util.Pair;
+
+import libcore.io.IoUtils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -28,12 +31,12 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.Comparator;
import java.util.Objects;
+import java.util.TreeSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
-import libcore.io.IoUtils;
-
/**
* Utility that rotates files over time, similar to {@code logrotate}. There is
* a single "active" file, which is periodically rotated into historical files,
@@ -302,17 +305,24 @@
public void readMatching(Reader reader, long matchStartMillis, long matchEndMillis)
throws IOException {
final FileInfo info = new FileInfo(mPrefix);
+ final TreeSet<Pair<Long, String>> readSet = new TreeSet<>(
+ Comparator.comparingLong(o -> o.first));
for (String name : mBasePath.list()) {
if (!info.parse(name)) continue;
- // read file when it overlaps
+ // Add file to set when it overlaps.
if (info.startMillis <= matchEndMillis && matchStartMillis <= info.endMillis) {
- if (LOGD) Log.d(TAG, "reading matching " + name);
-
- final File file = new File(mBasePath, name);
- readFile(file, reader);
+ readSet.add(new Pair(info.startMillis, name));
}
}
+
+ // Read files in ascending order of start timestamp.
+ for (Pair<Long, String> pair : readSet) {
+ final String name = pair.second;
+ if (LOGD) Log.d(TAG, "reading matching " + name);
+ final File file = new File(mBasePath, name);
+ readFile(file, reader);
+ }
}
/**
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c368fa8..56066b2 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1806,15 +1806,10 @@
if (!is_system_server && getuid() == 0) {
const int rc = createProcessGroup(uid, getpid());
if (rc != 0) {
- if (rc == -ESRCH) {
- // If process is dead, treat this as a non-fatal error
- ALOGE("createProcessGroup(%d, %d) failed: %s", uid, /* pid= */ 0, strerror(-rc));
- } else {
- fail_fn(rc == -EROFS ? CREATE_ERROR("createProcessGroup failed, kernel missing "
- "CONFIG_CGROUP_CPUACCT?")
- : CREATE_ERROR("createProcessGroup(%d, %d) failed: %s", uid,
- /* pid= */ 0, strerror(-rc)));
- }
+ fail_fn(rc == -EROFS ? CREATE_ERROR("createProcessGroup failed, kernel missing "
+ "CONFIG_CGROUP_CPUACCT?")
+ : CREATE_ERROR("createProcessGroup(%d, %d) failed: %s", uid,
+ /* pid= */ 0, strerror(-rc)));
}
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index ad88092..6c93680 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -139,6 +139,7 @@
optional SettingProto touch_gesture_enabled = 10 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto long_press_home_enabled = 11 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto search_press_hold_nav_handle_enabled = 12 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto search_long_press_home_enabled = 13 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Assist assist = 7;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7d2690e..e7764d8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5717,13 +5717,13 @@
<!-- Blur radius for the Option 3 in R.integer.config_letterboxBackgroundType. Values < 0 are
ignored and 0 is used. -->
- <dimen name="config_letterboxBackgroundWallpaperBlurRadius">24dp</dimen>
+ <dimen name="config_letterboxBackgroundWallpaperBlurRadius">38dp</dimen>
<!-- Alpha of a black translucent scrim showed over wallpaper letterbox background when
the Option 3 is selected for R.integer.config_letterboxBackgroundType.
Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead. -->
<item name="config_letterboxBackgroundWallaperDarkScrimAlpha" format="float" type="dimen">
- 0.75
+ 0.54
</item>
<!-- Corners appearance of the letterbox background.
diff --git a/core/tests/coretests/src/android/content/pm/SignatureTest.java b/core/tests/coretests/src/android/content/pm/SignatureTest.java
index fb0a435..4dd7b40 100644
--- a/core/tests/coretests/src/android/content/pm/SignatureTest.java
+++ b/core/tests/coretests/src/android/content/pm/SignatureTest.java
@@ -33,28 +33,44 @@
/** Cert B with valid syntax */
private static final Signature B = new Signature("308204a830820390a003020102020900a1573d0f45bea193300d06092a864886f70d0101050500308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d301e170d3131303931393138343232355a170d3339303230343138343232355a308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d30820120300d06092a864886f70d01010105000382010d00308201080282010100de1b51336afc909d8bcca5920fcdc8940578ec5c253898930e985481cfdea75ba6fc54b1f7bb492a03d98db471ab4200103a8314e60ee25fef6c8b83bc1b2b45b084874cffef148fa2001bb25c672b6beba50b7ac026b546da762ea223829a22b80ef286131f059d2c9b4ca71d54e515a8a3fd6bf5f12a2493dfc2619b337b032a7cf8bbd34b833f2b93aeab3d325549a93272093943bb59dfc0197ae4861ff514e019b73f5cf10023ad1a032adb4b9bbaeb4debecb4941d6a02381f1165e1ac884c1fca9525c5854dce2ad8ec839b8ce78442c16367efc07778a337d3ca2cdf9792ac722b95d67c345f1c00976ec372f02bfcbef0262cc512a6845e71cfea0d020103a381fc3081f9301d0603551d0e0416041478a0fc4517fb70ff52210df33c8d32290a44b2bb3081c90603551d230481c13081be801478a0fc4517fb70ff52210df33c8d32290a44b2bba1819aa48197308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d820900a1573d0f45bea193300c0603551d13040530030101ff300d06092a864886f70d01010505000382010100977302dfbf668d7c61841c9c78d2563bcda1b199e95e6275a799939981416909722713531157f3cdcfea94eea7bb79ca3ca972bd8058a36ad1919291df42d7190678d4ea47a4b9552c9dfb260e6d0d9129b44615cd641c1080580e8a990dd768c6ab500c3b964e185874e4105109d94c5bd8c405deb3cf0f7960a563bfab58169a956372167a7e2674a04c4f80015d8f7869a7a4139aecbbdca2abc294144ee01e4109f0e47a518363cf6e9bf41f7560e94bdd4a5d085234796b05c7a1389adfd489feec2a107955129d7991daa49afb3d327dc0dc4fe959789372b093a89c8dbfa41554f771c18015a6cb242a17e04d19d55d3b4664eae12caf2a11cd2b836e");
+ private boolean areExactMatch(Signature[] a, Signature[] b) throws Exception {
+ SigningDetails ad1 = new SigningDetails(a,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3);
+ SigningDetails bd1 = new SigningDetails(b,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3);
+ return Signature.areExactMatch(ad1, bd1);
+ }
+
public void testExactlyEqual() throws Exception {
- assertTrue(Signature.areExactMatch(asArray(A), asArray(A)));
- assertTrue(Signature.areExactMatch(asArray(M), asArray(M)));
+ assertTrue(areExactMatch(asArray(A), asArray(A)));
+ assertTrue(areExactMatch(asArray(M), asArray(M)));
- assertFalse(Signature.areExactMatch(asArray(A), asArray(B)));
- assertFalse(Signature.areExactMatch(asArray(A), asArray(M)));
- assertFalse(Signature.areExactMatch(asArray(M), asArray(A)));
+ assertFalse(areExactMatch(asArray(A), asArray(B)));
+ assertFalse(areExactMatch(asArray(A), asArray(M)));
+ assertFalse(areExactMatch(asArray(M), asArray(A)));
- assertTrue(Signature.areExactMatch(asArray(A, M), asArray(M, A)));
+ assertTrue(areExactMatch(asArray(A, M), asArray(M, A)));
+ }
+
+ private boolean areEffectiveMatch(Signature[] a, Signature[] b) throws Exception {
+ SigningDetails ad1 = new SigningDetails(a,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3);
+ SigningDetails bd1 = new SigningDetails(b,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3);
+ return Signature.areEffectiveMatch(ad1, bd1);
}
public void testEffectiveMatch() throws Exception {
- assertTrue(Signature.areEffectiveMatch(asArray(A), asArray(A)));
- assertTrue(Signature.areEffectiveMatch(asArray(M), asArray(M)));
+ assertTrue(areEffectiveMatch(asArray(A), asArray(A)));
+ assertTrue(areEffectiveMatch(asArray(M), asArray(M)));
- assertFalse(Signature.areEffectiveMatch(asArray(A), asArray(B)));
- assertTrue(Signature.areEffectiveMatch(asArray(A), asArray(M)));
- assertTrue(Signature.areEffectiveMatch(asArray(M), asArray(A)));
+ assertFalse(areEffectiveMatch(asArray(A), asArray(B)));
+ assertTrue(areEffectiveMatch(asArray(A), asArray(M)));
+ assertTrue(areEffectiveMatch(asArray(M), asArray(A)));
- assertTrue(Signature.areEffectiveMatch(asArray(A, M), asArray(M, A)));
- assertTrue(Signature.areEffectiveMatch(asArray(A, B), asArray(M, B)));
- assertFalse(Signature.areEffectiveMatch(asArray(A, M), asArray(A, B)));
+ assertTrue(areEffectiveMatch(asArray(A, M), asArray(M, A)));
+ assertTrue(areEffectiveMatch(asArray(A, B), asArray(M, B)));
+ assertFalse(areEffectiveMatch(asArray(A, M), asArray(A, B)));
}
public void testHashCode_doesNotIncludeFlags() throws Exception {
diff --git a/core/tests/utiltests/src/com/android/internal/util/FileRotatorTest.java b/core/tests/utiltests/src/com/android/internal/util/FileRotatorTest.java
index 95272132..73e47e16 100644
--- a/core/tests/utiltests/src/com/android/internal/util/FileRotatorTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/FileRotatorTest.java
@@ -366,6 +366,16 @@
assertReadAll(rotate, "bar");
}
+ public void testReadSorted() throws Exception {
+ write("rotator.1024-2048", "2");
+ write("rotator.2048-4096", "3");
+ write("rotator.512-1024", "1");
+
+ final FileRotator rotate = new FileRotator(
+ mBasePath, PREFIX, SECOND_IN_MILLIS, SECOND_IN_MILLIS);
+ assertReadAll(rotate, "1", "2", "3");
+ }
+
public void testFileSystemInaccessible() throws Exception {
File inaccessibleDir = null;
String dirPath = getContext().getFilesDir() + File.separator + "inaccessible";
@@ -422,16 +432,7 @@
}
public void assertRead(String... expected) {
- assertEquals(expected.length, mActual.size());
-
- final ArrayList<String> actualCopy = new ArrayList<String>(mActual);
- for (String value : expected) {
- if (!actualCopy.remove(value)) {
- final String expectedString = Arrays.toString(expected);
- final String actualString = Arrays.toString(mActual.toArray());
- fail("expected: " + expectedString + " but was: " + actualString);
- }
- }
+ assertEquals(Arrays.asList(expected), mActual);
}
}
}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 6057852..ee2eacf 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1021,6 +1021,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.java"
},
+ "-1152771606": {
+ "message": "Content Recording: Display %d was already recording, but pause capture since the task is in PIP",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-1145384901": {
"message": "shouldWaitAnimatingExit: isTransition: %s",
"level": "DEBUG",
@@ -2335,6 +2341,12 @@
"group": "WM_DEBUG_SYNC_ENGINE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "1877956": {
+ "message": "Content Recording: Display %d should start recording, but don't yet since the task is in PIP",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"3593205": {
"message": "commitVisibility: %s: visible=%b mVisibleRequested=%b",
"level": "VERBOSE",
@@ -3067,6 +3079,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "643263584": {
+ "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop %d x %d for display %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"644675193": {
"message": "Real start recents",
"level": "DEBUG",
diff --git a/data/keyboards/qwerty.idc b/data/keyboards/qwerty.idc
deleted file mode 100644
index 375d785..0000000
--- a/data/keyboards/qwerty.idc
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#
-# Emulator keyboard configuration file #1.
-#
-
-touch.deviceType = touchScreen
-touch.orientationAware = 1
-
-keyboard.layout = qwerty
-keyboard.characterMap = qwerty
-keyboard.orientationAware = 1
-keyboard.builtIn = 1
-
-cursor.mode = navigation
-cursor.orientationAware = 1
diff --git a/data/keyboards/qwerty.kcm b/data/keyboards/qwerty.kcm
deleted file mode 100644
index f3e15241..0000000
--- a/data/keyboards/qwerty.kcm
+++ /dev/null
@@ -1,508 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#
-# Emulator keyboard character map #1.
-#
-# This file is no longer used as the platform's default keyboard character map.
-# Refer to Generic.kcm and Virtual.kcm instead.
-#
-
-type ALPHA
-
-key A {
- label: 'A'
- number: '2'
- base: 'a'
- shift, capslock: 'A'
- alt: '#'
- shift+alt, capslock+alt: none
-}
-
-key B {
- label: 'B'
- number: '2'
- base: 'b'
- shift, capslock: 'B'
- alt: '<'
- shift+alt, capslock+alt: none
-}
-
-key C {
- label: 'C'
- number: '2'
- base: 'c'
- shift, capslock: 'C'
- alt: '9'
- shift+alt, capslock+alt: '\u00e7'
-}
-
-key D {
- label: 'D'
- number: '3'
- base: 'd'
- shift, capslock: 'D'
- alt: '5'
- shift+alt, capslock+alt: none
-}
-
-key E {
- label: 'E'
- number: '3'
- base: 'e'
- shift, capslock: 'E'
- alt: '2'
- shift+alt, capslock+alt: '\u0301'
-}
-
-key F {
- label: 'F'
- number: '3'
- base: 'f'
- shift, capslock: 'F'
- alt: '6'
- shift+alt, capslock+alt: '\u00a5'
-}
-
-key G {
- label: 'G'
- number: '4'
- base: 'g'
- shift, capslock: 'G'
- alt: '-'
- shift+alt, capslock+alt: '_'
-}
-
-key H {
- label: 'H'
- number: '4'
- base: 'h'
- shift, capslock: 'H'
- alt: '['
- shift+alt, capslock+alt: '{'
-}
-
-key I {
- label: 'I'
- number: '4'
- base: 'i'
- shift, capslock: 'I'
- alt: '$'
- shift+alt, capslock+alt: '\u0302'
-}
-
-key J {
- label: 'J'
- number: '5'
- base: 'j'
- shift, capslock: 'J'
- alt: ']'
- shift+alt, capslock+alt: '}'
-}
-
-key K {
- label: 'K'
- number: '5'
- base: 'k'
- shift, capslock: 'K'
- alt: '"'
- shift+alt, capslock+alt: '~'
-}
-
-key L {
- label: 'L'
- number: '5'
- base: 'l'
- shift, capslock: 'L'
- alt: '\''
- shift+alt, capslock+alt: '`'
-}
-
-key M {
- label: 'M'
- number: '6'
- base: 'm'
- shift, capslock: 'M'
- alt: '!'
- shift+alt, capslock+alt: none
-}
-
-key N {
- label: 'N'
- number: '6'
- base: 'n'
- shift, capslock: 'N'
- alt: '>'
- shift+alt, capslock+alt: '\u0303'
-}
-
-key O {
- label: 'O'
- number: '6'
- base: 'o'
- shift, capslock: 'O'
- alt: '('
- shift+alt, capslock+alt: none
-}
-
-key P {
- label: 'P'
- number: '7'
- base: 'p'
- shift, capslock: 'P'
- alt: ')'
- shift+alt, capslock+alt: none
-}
-
-key Q {
- label: 'Q'
- number: '7'
- base: 'q'
- shift, capslock: 'Q'
- alt: '*'
- shift+alt, capslock+alt: '\u0300'
-}
-
-key R {
- label: 'R'
- number: '7'
- base: 'r'
- shift, capslock: 'R'
- alt: '3'
- shift+alt, capslock+alt: '\u20ac'
-}
-
-key S {
- label: 'S'
- number: '7'
- base: 's'
- shift, capslock: 'S'
- alt: '4'
- shift+alt, capslock+alt: '\u00df'
-}
-
-key T {
- label: 'T'
- number: '8'
- base: 't'
- shift, capslock: 'T'
- alt: '+'
- shift+alt, capslock+alt: '\u00a3'
-}
-
-key U {
- label: 'U'
- number: '8'
- base: 'u'
- shift, capslock: 'U'
- alt: '&'
- shift+alt, capslock+alt: '\u0308'
-}
-
-key V {
- label: 'V'
- number: '8'
- base: 'v'
- shift, capslock: 'V'
- alt: '='
- shift+alt, capslock+alt: '^'
-}
-
-key W {
- label: 'W'
- number: '9'
- base: 'w'
- shift, capslock: 'W'
- alt: '1'
- shift+alt, capslock+alt: none
-}
-
-key X {
- label: 'X'
- number: '9'
- base: 'x'
- shift, capslock: 'X'
- alt: '8'
- shift+alt, capslock+alt: '\uef00'
-}
-
-key Y {
- label: 'Y'
- number: '9'
- base: 'y'
- shift, capslock: 'Y'
- alt: '%'
- shift+alt, capslock+alt: '\u00a1'
-}
-
-key Z {
- label: 'Z'
- number: '9'
- base: 'z'
- shift, capslock: 'Z'
- alt: '7'
- shift+alt, capslock+alt: none
-}
-
-key COMMA {
- label: ','
- number: ','
- base: ','
- shift: ';'
- alt: ';'
- shift+alt: '|'
-}
-
-key PERIOD {
- label: '.'
- number: '.'
- base: '.'
- shift: ':'
- alt: ':'
- shift+alt: '\u2026'
-}
-
-key AT {
- label: '@'
- number: '0'
- base: '@'
- shift: '0'
- alt: '0'
- shift+alt: '\u2022'
-}
-
-key SLASH {
- label: '/'
- number: '/'
- base: '/'
- shift: '?'
- alt: '?'
- shift+alt: '\\'
-}
-
-key SPACE {
- label: ' '
- number: ' '
- base: ' '
- shift: ' '
- alt: '\uef01'
- shift+alt: '\uef01'
-}
-
-key ENTER {
- label: '\n'
- number: '\n'
- base: '\n'
- shift: '\n'
- alt: '\n'
- shift+alt: '\n'
-}
-
-key TAB {
- label: '\t'
- number: '\t'
- base: '\t'
- shift: '\t'
- alt: '\t'
- shift+alt: '\t'
-}
-
-key 0 {
- label: '0'
- number: '0'
- base: '0'
- shift: ')'
- alt: ')'
- shift+alt: ')'
-}
-
-key 1 {
- label: '1'
- number: '1'
- base: '1'
- shift: '!'
- alt: '!'
- shift+alt: '!'
-}
-
-key 2 {
- label: '2'
- number: '2'
- base: '2'
- shift: '@'
- alt: '@'
- shift+alt: '@'
-}
-
-key 3 {
- label: '3'
- number: '3'
- base: '3'
- shift: '#'
- alt: '#'
- shift+alt: '#'
-}
-
-key 4 {
- label: '4'
- number: '4'
- base: '4'
- shift: '$'
- alt: '$'
- shift+alt: '$'
-}
-
-key 5 {
- label: '5'
- number: '5'
- base: '5'
- shift: '%'
- alt: '%'
- shift+alt: '%'
-}
-
-key 6 {
- label: '6'
- number: '6'
- base: '6'
- shift: '^'
- alt: '^'
- shift+alt: '^'
-}
-
-key 7 {
- label: '7'
- number: '7'
- base: '7'
- shift: '&'
- alt: '&'
- shift+alt: '&'
-}
-
-key 8 {
- label: '8'
- number: '8'
- base: '8'
- shift: '*'
- alt: '*'
- shift+alt: '*'
-}
-
-key 9 {
- label: '9'
- number: '9'
- base: '9'
- shift: '('
- alt: '('
- shift+alt: '('
-}
-
-key GRAVE {
- label: '`'
- number: '`'
- base: '`'
- shift: '~'
- alt: '`'
- shift+alt: '~'
-}
-
-key MINUS {
- label: '-'
- number: '-'
- base: '-'
- shift: '_'
- alt: '-'
- shift+alt: '_'
-}
-
-key EQUALS {
- label: '='
- number: '='
- base: '='
- shift: '+'
- alt: '='
- shift+alt: '+'
-}
-
-key LEFT_BRACKET {
- label: '['
- number: '['
- base: '['
- shift: '{'
- alt: '['
- shift+alt: '{'
-}
-
-key RIGHT_BRACKET {
- label: ']'
- number: ']'
- base: ']'
- shift: '}'
- alt: ']'
- shift+alt: '}'
-}
-
-key BACKSLASH {
- label: '\\'
- number: '\\'
- base: '\\'
- shift: '|'
- alt: '\\'
- shift+alt: '|'
-}
-
-key SEMICOLON {
- label: ';'
- number: ';'
- base: ';'
- shift: ':'
- alt: ';'
- shift+alt: ':'
-}
-
-key APOSTROPHE {
- label: '\''
- number: '\''
- base: '\''
- shift: '"'
- alt: '\''
- shift+alt: '"'
-}
-
-key STAR {
- label: '*'
- number: '*'
- base: '*'
- shift: '*'
- alt: '*'
- shift+alt: '*'
-}
-
-key POUND {
- label: '#'
- number: '#'
- base: '#'
- shift: '#'
- alt: '#'
- shift+alt: '#'
-}
-
-key PLUS {
- label: '+'
- number: '+'
- base: '+'
- shift: '+'
- alt: '+'
- shift+alt: '+'
-}
diff --git a/data/keyboards/qwerty.kl b/data/keyboards/qwerty.kl
deleted file mode 100644
index 2fd99ab..0000000
--- a/data/keyboards/qwerty.kl
+++ /dev/null
@@ -1,131 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#
-# Emulator keyboard layout #1.
-#
-# This file is no longer used as the platform's default keyboard layout.
-# Refer to Generic.kl instead.
-#
-
-key 399 GRAVE
-key 2 1
-key 3 2
-key 4 3
-key 5 4
-key 6 5
-key 7 6
-key 8 7
-key 9 8
-key 10 9
-key 11 0
-key 158 BACK
-key 230 SOFT_RIGHT
-key 60 SOFT_LEFT
-key 107 ENDCALL
-key 62 ENDCALL
-key 229 MENU
-key 139 MENU
-key 59 MENU
-key 127 SEARCH
-key 217 SEARCH
-key 228 POUND
-key 227 STAR
-key 231 CALL
-key 61 CALL
-key 232 DPAD_CENTER
-key 108 DPAD_DOWN
-key 103 DPAD_UP
-key 102 HOME
-key 105 DPAD_LEFT
-key 106 DPAD_RIGHT
-key 115 VOLUME_UP
-key 114 VOLUME_DOWN
-key 116 POWER
-key 212 CAMERA
-
-key 16 Q
-key 17 W
-key 18 E
-key 19 R
-key 20 T
-key 21 Y
-key 22 U
-key 23 I
-key 24 O
-key 25 P
-key 26 LEFT_BRACKET
-key 27 RIGHT_BRACKET
-key 43 BACKSLASH
-
-key 30 A
-key 31 S
-key 32 D
-key 33 F
-key 34 G
-key 35 H
-key 36 J
-key 37 K
-key 38 L
-key 39 SEMICOLON
-key 40 APOSTROPHE
-key 14 DEL
-
-key 44 Z
-key 45 X
-key 46 C
-key 47 V
-key 48 B
-key 49 N
-key 50 M
-key 51 COMMA
-key 52 PERIOD
-key 53 SLASH
-key 28 ENTER
-
-key 56 ALT_LEFT
-key 100 ALT_RIGHT
-key 42 SHIFT_LEFT
-key 54 SHIFT_RIGHT
-key 15 TAB
-key 57 SPACE
-key 150 EXPLORER
-key 155 ENVELOPE
-
-key 12 MINUS
-key 13 EQUALS
-key 215 AT
-
-# On an AT keyboard: ESC, F10
-key 1 BACK
-key 68 MENU
-
-# App switch = Overview key
-key 580 APP_SWITCH
-
-# Media control keys
-key 160 MEDIA_CLOSE
-key 161 MEDIA_EJECT
-key 163 MEDIA_NEXT
-key 164 MEDIA_PLAY_PAUSE
-key 165 MEDIA_PREVIOUS
-key 166 MEDIA_STOP
-key 167 MEDIA_RECORD
-key 168 MEDIA_REWIND
-
-key 142 SLEEP
-key 581 STEM_PRIMARY
-key 582 STEM_1
-key 583 STEM_2
-key 584 STEM_3
diff --git a/data/keyboards/qwerty2.idc b/data/keyboards/qwerty2.idc
deleted file mode 100644
index 369205e..0000000
--- a/data/keyboards/qwerty2.idc
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#
-# Emulator keyboard configuration file #2.
-#
-
-touch.deviceType = touchScreen
-touch.orientationAware = 1
-
-keyboard.layout = qwerty
-keyboard.characterMap = qwerty2
-keyboard.orientationAware = 1
-keyboard.builtIn = 1
-
-cursor.mode = navigation
-cursor.orientationAware = 1
diff --git a/data/keyboards/qwerty2.kcm b/data/keyboards/qwerty2.kcm
deleted file mode 100644
index b981d83..0000000
--- a/data/keyboards/qwerty2.kcm
+++ /dev/null
@@ -1,505 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#
-# Emulator keyboard character map #2.
-#
-
-type ALPHA
-
-key A {
- label: 'A'
- number: '2'
- base: 'a'
- shift, capslock: 'A'
- alt: '\u00e1'
- shift+alt, capslock+alt: '\u00c1'
-}
-
-key B {
- label: 'B'
- number: '2'
- base: 'b'
- shift, capslock: 'B'
- alt: 'b'
- shift+alt, capslock+alt: 'B'
-}
-
-key C {
- label: 'C'
- number: '2'
- base: 'c'
- shift, capslock: 'C'
- alt: '\u00a9'
- shift+alt, capslock+alt: '\u00a2'
-}
-
-key D {
- label: 'D'
- number: '3'
- base: 'd'
- shift, capslock: 'D'
- alt: '\u00f0'
- shift+alt, capslock+alt: '\u00d0'
-}
-
-key E {
- label: 'E'
- number: '3'
- base: 'e'
- shift, capslock: 'E'
- alt: '\u00e9'
- shift+alt, capslock+alt: '\u00c9'
-}
-
-key F {
- label: 'F'
- number: '3'
- base: 'f'
- shift, capslock: 'F'
- alt: '['
- shift+alt, capslock+alt: '['
-}
-
-key G {
- label: 'G'
- number: '4'
- base: 'g'
- shift, capslock: 'G'
- alt: ']'
- shift+alt, capslock+alt: ']'
-}
-
-key H {
- label: 'H'
- number: '4'
- base: 'h'
- shift, capslock: 'H'
- alt: '<'
- shift+alt, capslock+alt: '<'
-}
-
-key I {
- label: 'I'
- number: '4'
- base: 'i'
- shift, capslock: 'I'
- alt: '\u00ed'
- shift+alt, capslock+alt: '\u00cd'
-}
-
-key J {
- label: 'J'
- number: '5'
- base: 'j'
- shift, capslock: 'J'
- alt: '>'
- shift+alt, capslock+alt: '>'
-}
-
-key K {
- label: 'K'
- number: '5'
- base: 'k'
- shift, capslock: 'K'
- alt: ';'
- shift+alt, capslock+alt: '~'
-}
-
-key L {
- label: 'L'
- number: '5'
- base: 'l'
- shift, capslock: 'L'
- alt: '\u00f8'
- shift+alt, capslock+alt: '\u00d8'
-}
-
-key M {
- label: 'M'
- number: '6'
- base: 'm'
- shift, capslock: 'M'
- alt: '\u00b5'
- shift+alt, capslock+alt: none
-}
-
-key N {
- label: 'N'
- number: '6'
- base: 'n'
- shift, capslock: 'N'
- alt: '\u00f1'
- shift+alt, capslock+alt: '\u00d1'
-}
-
-key O {
- label: 'O'
- number: '6'
- base: 'o'
- shift, capslock: 'O'
- alt: '\u00f3'
- shift+alt, capslock+alt: '\u00d3'
-}
-
-key P {
- label: 'P'
- number: '7'
- base: 'p'
- shift, capslock: 'P'
- alt: '\u00f6'
- shift+alt, capslock+alt: '\u00d6'
-}
-
-key Q {
- label: 'Q'
- number: '7'
- base: 'q'
- shift, capslock: 'Q'
- alt: '\u00e4'
- shift+alt, capslock+alt: '\u00c4'
-}
-
-key R {
- label: 'R'
- number: '7'
- base: 'r'
- shift, capslock: 'R'
- alt: '\u00ae'
- shift+alt, capslock+alt: 'R'
-}
-
-key S {
- label: 'S'
- number: '7'
- base: 's'
- shift, capslock: 'S'
- alt: '\u00df'
- shift+alt, capslock+alt: '\u00a7'
-}
-
-key T {
- label: 'T'
- number: '8'
- base: 't'
- shift, capslock: 'T'
- alt: '\u00fe'
- shift+alt, capslock+alt: '\u00de'
-}
-
-key U {
- label: 'U'
- number: '8'
- base: 'u'
- shift, capslock: 'U'
- alt: '\u00fa'
- shift+alt, capslock+alt: '\u00da'
-}
-
-key V {
- label: 'V'
- number: '8'
- base: 'v'
- shift, capslock: 'V'
- alt: 'v'
- shift+alt, capslock+alt: 'V'
-}
-
-key W {
- label: 'W'
- number: '9'
- base: 'w'
- shift, capslock: 'W'
- alt: '\u00e5'
- shift+alt, capslock+alt: '\u00c5'
-}
-
-key X {
- label: 'X'
- number: '9'
- base: 'x'
- shift, capslock: 'X'
- alt: 'x'
- shift+alt, capslock+alt: '\uef00'
-}
-
-key Y {
- label: 'Y'
- number: '9'
- base: 'y'
- shift, capslock: 'Y'
- alt: '\u00fc'
- shift+alt, capslock+alt: '\u00dc'
-}
-
-key Z {
- label: 'Z'
- number: '9'
- base: 'z'
- shift, capslock: 'Z'
- alt: '\u00e6'
- shift+alt, capslock+alt: '\u00c6'
-}
-
-key COMMA {
- label: ','
- number: ','
- base: ','
- shift: '<'
- alt: '\u00e7'
- shift+alt: '\u00c7'
-}
-
-key PERIOD {
- label: '.'
- number: '.'
- base: '.'
- shift: '>'
- alt: '.'
- shift+alt: '\u2026'
-}
-
-key AT {
- label: '@'
- number: '@'
- base: '@'
- shift: '@'
- alt: '@'
- shift+alt: '\u2022'
-}
-
-key SLASH {
- label: '/'
- number: '/'
- base: '/'
- shift: '?'
- alt: '\u00bf'
- shift+alt: '?'
-}
-
-key SPACE {
- label: ' '
- number: ' '
- base: ' '
- shift: ' '
- alt: '\uef01'
- shift+alt: '\uef01'
-}
-
-key ENTER {
- label: '\n'
- number: '\n'
- base: '\n'
- shift: '\n'
- alt: '\n'
- shift+alt: '\n'
-}
-
-key TAB {
- label: '\t'
- number: '\t'
- base: '\t'
- shift: '\t'
- alt: '\t'
- shift+alt: '\t'
-}
-
-key 0 {
- label: '0'
- number: '0'
- base: '0'
- shift: ')'
- alt: '\u02bc'
- shift+alt: ')'
-}
-
-key 1 {
- label: '1'
- number: '1'
- base: '1'
- shift: '!'
- alt: '\u00a1'
- shift+alt: '\u00b9'
-}
-
-key 2 {
- label: '2'
- number: '2'
- base: '2'
- shift: '@'
- alt: '\u00b2'
- shift+alt: '@'
-}
-
-key 3 {
- label: '3'
- number: '3'
- base: '3'
- shift: '#'
- alt: '\u00b3'
- shift+alt: '#'
-}
-
-key 4 {
- label: '4'
- number: '4'
- base: '4'
- shift: '$'
- alt: '\u00a4'
- shift+alt: '\u00a3'
-}
-
-key 5 {
- label: '5'
- number: '5'
- base: '5'
- shift: '%'
- alt: '\u20ac'
- shift+alt: '%'
-}
-
-key 6 {
- label: '6'
- number: '6'
- base: '6'
- shift: '^'
- alt: '\u00bc'
- shift+alt: '\u0302'
-}
-
-key 7 {
- label: '7'
- number: '7'
- base: '7'
- shift: '&'
- alt: '\u00bd'
- shift+alt: '&'
-}
-
-key 8 {
- label: '8'
- number: '8'
- base: '8'
- shift: '*'
- alt: '\u00be'
- shift+alt: '*'
-}
-
-key 9 {
- label: '9'
- number: '9'
- base: '9'
- shift: '('
- alt: '\u02bb'
- shift+alt: '('
-}
-
-key GRAVE {
- label: '`'
- number: '`'
- base: '`'
- shift: '~'
- alt: '\u0300'
- shift+alt: '\u0303'
-}
-
-key MINUS {
- label: '-'
- number: '-'
- base: '-'
- shift: '_'
- alt: '\u00a5'
- shift+alt: '_'
-}
-
-key EQUALS {
- label: '='
- number: '='
- base: '='
- shift: '+'
- alt: '\u00d7'
- shift+alt: '\u00f7'
-}
-
-key LEFT_BRACKET {
- label: '['
- number: '['
- base: '['
- shift: '{'
- alt: '\u00ab'
- shift+alt: '{'
-}
-
-key RIGHT_BRACKET {
- label: ']'
- number: ']'
- base: ']'
- shift: '}'
- alt: '\u00bb'
- shift+alt: '}'
-}
-
-key BACKSLASH {
- label: '\\'
- number: '\\'
- base: '\\'
- shift: '|'
- alt: '\u00ac'
- shift+alt: '\u00a6'
-}
-
-key SEMICOLON {
- label: ';'
- number: ';'
- base: ';'
- shift: ':'
- alt: '\u00b6'
- shift+alt: '\u00b0'
-}
-
-key APOSTROPHE {
- label: '\''
- number: '\''
- base: '\''
- shift: '"'
- alt: '\u0301'
- shift+alt: '\u0308'
-}
-
-key STAR {
- label: '*'
- number: '*'
- base: '*'
- shift: '*'
- alt: '*'
- shift+alt: '*'
-}
-
-key POUND {
- label: '#'
- number: '#'
- base: '#'
- shift: '#'
- alt: '#'
- shift+alt: '#'
-}
-
-key PLUS {
- label: '+'
- number: '+'
- base: '+'
- shift: '+'
- alt: '+'
- shift+alt: '+'
-}
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 7b204f2..13540e0 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -16,6 +16,9 @@
package android.graphics.text;
+import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,6 +37,56 @@
public final class LineBreakConfig {
/**
+ * No hyphenation preference is specified.
+ *
+ * This is a special value of hyphenation preference indicating no hyphenation preference is
+ * specified. When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig}
+ * with {@link Builder#merge(LineBreakConfig)} function, the hyphenation preference of
+ * overridden config will be kept if the hyphenation preference of overriding config is
+ * {@link #HYPHENATION_UNSPECIFIED}.
+ *
+ * <pre>
+ * val override = LineBreakConfig.Builder()
+ * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+ * .build(); // UNSPECIFIED if no setHyphenation is called.
+ * val config = LineBreakConfig.Builder()
+ * .setHyphenation(LineBreakConfig.HYPHENATION_DISABLED)
+ * .merge(override)
+ * .build()
+ * // Here, config has HYPHENATION_DISABLED for line break config and
+ * // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
+ * </pre>
+ *
+ * This value is resolved to {@link #HYPHENATION_ENABLED} if this value is used for text
+ * layout/rendering.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public static final int HYPHENATION_UNSPECIFIED = -1;
+
+ /**
+ * The hyphenation is disabled.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public static final int HYPHENATION_DISABLED = 0;
+
+ /**
+ * The hyphenation is enabled.
+ *
+ * Note: Even if the hyphenation is enabled with a line break strategy
+ * {@link LineBreaker#BREAK_STRATEGY_SIMPLE}, the hyphenation will not be performed unless a
+ * single word cannot meet width constraints.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public static final int HYPHENATION_ENABLED = 1;
+
+ /** @hide */
+ @IntDef(prefix = { "HYPHENATION_" }, value = {
+ HYPHENATION_UNSPECIFIED, HYPHENATION_ENABLED, HYPHENATION_DISABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Hyphenation {}
+
+ /**
* No line break style is specified.
*
* This is a special value of line break style indicating no style value is specified.
@@ -147,6 +200,8 @@
private @LineBreakWordStyle int mLineBreakWordStyle =
LineBreakConfig.LINE_BREAK_WORD_STYLE_UNSPECIFIED;
+ private @Hyphenation int mHyphenation = LineBreakConfig.HYPHENATION_UNSPECIFIED;
+
/**
* Builder constructor.
*/
@@ -188,6 +243,9 @@
if (config.mLineBreakWordStyle != LINE_BREAK_WORD_STYLE_UNSPECIFIED) {
mLineBreakWordStyle = config.mLineBreakWordStyle;
}
+ if (config.mHyphenation != HYPHENATION_UNSPECIFIED) {
+ mHyphenation = config.mHyphenation;
+ }
return this;
}
@@ -201,9 +259,11 @@
if (config == null) {
mLineBreakStyle = LINE_BREAK_STYLE_UNSPECIFIED;
mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_UNSPECIFIED;
+ mHyphenation = HYPHENATION_UNSPECIFIED;
} else {
mLineBreakStyle = config.mLineBreakStyle;
mLineBreakWordStyle = config.mLineBreakWordStyle;
+ mHyphenation = config.mHyphenation;
}
return this;
}
@@ -215,6 +275,11 @@
* {@link #LINE_BREAK_STYLE_UNSPECIFIED}, the line break style is reset to
* {@link #LINE_BREAK_STYLE_UNSPECIFIED}.
*
+ * @see <a href="https://unicode.org/reports/tr35/#UnicodeLineBreakStyleIdentifier">
+ * Unicode Line Break Style Identifier</a>
+ * @see <a href="https://drafts.csswg.org/css-text/#line-break-property">
+ * CSS Line Break Property</a>
+ *
* @param lineBreakStyle The new line-break style.
* @return This {@code Builder}.
*/
@@ -230,6 +295,11 @@
* with {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}, the line break style is reset to
* {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}.
*
+ * @see <a href="https://unicode.org/reports/tr35/#UnicodeLineBreakWordIdentifier">
+ * Unicode Line Break Word Identifier</a>
+ * @see <a href="https://drafts.csswg.org/css-text/#word-break-property">
+ * CSS Word Break Property</a>
+ *
* @param lineBreakWordStyle The new line-break word style.
* @return This {@code Builder}.
*/
@@ -239,6 +309,27 @@
}
/**
+ * Sets the hyphenation preference
+ *
+ * Note: Even if the {@link LineBreakConfig#HYPHENATION_ENABLED} is specified, the
+ * hyphenation will not be performed if the {@link android.widget.TextView} or underlying
+ * {@link android.text.StaticLayout}, {@link LineBreaker} are configured with
+ * {@link LineBreaker#HYPHENATION_FREQUENCY_NONE}.
+ *
+ * Note: Even if the hyphenation is enabled with a line break strategy
+ * {@link LineBreaker#BREAK_STRATEGY_SIMPLE}, the hyphenation will not be performed unless a
+ * single word cannot meet width constraints.
+ *
+ * @param hyphenation The hyphenation preference.
+ * @return This {@code Builder}.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public @NonNull Builder setHyphenation(@Hyphenation int hyphenation) {
+ mHyphenation = hyphenation;
+ return this;
+ }
+
+ /**
* Builds a {@link LineBreakConfig} instance.
*
* This method can be called multiple times for generating multiple {@link LineBreakConfig}
@@ -247,7 +338,7 @@
* @return The {@code LineBreakConfig} instance.
*/
public @NonNull LineBreakConfig build() {
- return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle);
+ return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mHyphenation);
}
}
@@ -275,6 +366,7 @@
private final @LineBreakStyle int mLineBreakStyle;
private final @LineBreakWordStyle int mLineBreakWordStyle;
+ private final @Hyphenation int mHyphenation;
/**
* Constructor with line-break parameters.
@@ -283,9 +375,11 @@
* {@code LineBreakConfig} instance.
*/
private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
- @LineBreakWordStyle int lineBreakWordStyle) {
+ @LineBreakWordStyle int lineBreakWordStyle,
+ @Hyphenation int hyphenation) {
mLineBreakStyle = lineBreakStyle;
mLineBreakWordStyle = lineBreakWordStyle;
+ mHyphenation = hyphenation;
}
/**
@@ -340,6 +434,34 @@
}
/**
+ * Returns a hyphenation preference.
+ *
+ * @return A hyphenation preference.
+ */
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public @Hyphenation int getHyphenation() {
+ return mHyphenation;
+ }
+
+ /**
+ * Returns a hyphenation preference.
+ *
+ * This method never returns {@link #HYPHENATION_UNSPECIFIED}.
+ *
+ * @return A hyphenation preference.
+ * @hide
+ */
+ public static @Hyphenation int getResolvedHyphenation(
+ @Nullable LineBreakConfig config) {
+ if (config == null) {
+ return HYPHENATION_ENABLED;
+ }
+ return config.mHyphenation == HYPHENATION_UNSPECIFIED
+ ? HYPHENATION_ENABLED : config.mHyphenation;
+ }
+
+
+ /**
* Generates a new {@link LineBreakConfig} instance merged with given {@code config}.
*
* If values of passing {@code config} are unspecified, the original values are kept. For
@@ -366,7 +488,9 @@
config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED
? mLineBreakStyle : config.mLineBreakStyle,
config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED
- ? mLineBreakWordStyle : config.mLineBreakWordStyle);
+ ? mLineBreakWordStyle : config.mLineBreakWordStyle,
+ config.mHyphenation == HYPHENATION_UNSPECIFIED
+ ? mHyphenation : config.mHyphenation);
}
@Override
@@ -376,12 +500,13 @@
if (!(o instanceof LineBreakConfig)) return false;
LineBreakConfig that = (LineBreakConfig) o;
return (mLineBreakStyle == that.mLineBreakStyle)
- && (mLineBreakWordStyle == that.mLineBreakWordStyle);
+ && (mLineBreakWordStyle == that.mLineBreakWordStyle)
+ && (mHyphenation == that.mHyphenation);
}
@Override
public int hashCode() {
- return Objects.hash(mLineBreakStyle, mLineBreakWordStyle);
+ return Objects.hash(mLineBreakStyle, mLineBreakWordStyle, mHyphenation);
}
@Override
@@ -389,6 +514,7 @@
return "LineBreakConfig{"
+ "mLineBreakStyle=" + mLineBreakStyle
+ ", mLineBreakWordStyle=" + mLineBreakWordStyle
+ + ", mHyphenation= " + mHyphenation
+ '}';
}
}
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 8317985..2d33e8d 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -301,7 +301,9 @@
Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length");
int lbStyle = LineBreakConfig.getResolvedLineBreakStyle(lineBreakConfig);
int lbWordStyle = LineBreakConfig.getResolvedLineBreakWordStyle(lineBreakConfig);
- nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle,
+ boolean hyphenation = LineBreakConfig.getResolvedHyphenation(lineBreakConfig)
+ == LineBreakConfig.HYPHENATION_ENABLED;
+ nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle, hyphenation,
mCurrentOffset, end, isRtl);
mCurrentOffset = end;
@@ -510,6 +512,7 @@
/* Non Zero */ long paintPtr,
int lineBreakStyle,
int lineBreakWordStyle,
+ boolean hyphenation,
@IntRange(from = 0) int start,
@IntRange(from = 0) int end,
boolean isRtl);
diff --git a/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml b/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml
new file mode 100644
index 0000000..8ef3307
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M4,18H20V6H4V18ZM22,18C22,19.1 21.1,20 20,20H4C2.9,20 2,19.1 2,18V6C2,4.9 2.9,4 4,4H20C21.1,4 22,4.9 22,6V18ZM13,8H18V14H13V8Z"
+ android:fillColor="#455A64"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
new file mode 100644
index 0000000..b489a5c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<com.android.wm.shell.common.bubbles.BubblePopupView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:layout_margin="@dimen/bubble_popup_margin_horizontal"
+ android:layout_marginBottom="120dp"
+ android:elevation="@dimen/bubble_manage_menu_elevation"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+ <ImageView
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:tint="?android:attr/colorAccent"
+ android:contentDescription="@null"
+ android:src="@drawable/ic_floating_landscape"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:maxWidth="@dimen/bubble_popup_content_max_width"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
+ android:textColor="?android:attr/textColorPrimary"
+ android:text="@string/bubble_bar_education_stack_title"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:maxWidth="@dimen/bubble_popup_content_max_width"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAlignment="center"
+ android:text="@string/bubble_bar_education_stack_text"/>
+
+</com.android.wm.shell.common.bubbles.BubblePopupView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 00c63d7..eabe3a4 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -163,6 +163,10 @@
<!-- [CHAR LIMIT=NONE] Empty overflow subtitle -->
<string name="bubble_overflow_empty_subtitle">Recent bubbles and dismissed bubbles will appear here</string>
+ <!-- Title text for the bubble bar feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]-->
+ <string name="bubble_bar_education_stack_title">Chat using bubbles</string>
+ <!-- Descriptive text for the bubble bar feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=NONE] -->
+ <string name="bubble_bar_education_stack_text">New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them.</string>
<!-- Title text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=60]-->
<string name="bubble_bar_education_manage_title">Control bubbles anytime</string>
<!-- Descriptive text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=80]-->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index dfdc79e..f259902 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -56,6 +56,7 @@
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.os.Binder;
@@ -1063,6 +1064,15 @@
}
}
+ /**
+ * Show bubble bar user education relative to the reference position.
+ * @param position the reference position in Screen coordinates.
+ */
+ public void showUserEducation(Point position) {
+ if (mLayerView == null) return;
+ mLayerView.showUserEducation(position);
+ }
+
@VisibleForTesting
public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
@@ -1115,6 +1125,16 @@
}
/**
+ * Expands the stack if the selected bubble is present. This is currently used when user
+ * education view is clicked to expand the selected bubble.
+ */
+ public void expandStackWithSelectedBubble() {
+ if (mBubbleData.getSelectedBubble() != null) {
+ mBubbleData.setExpanded(true);
+ }
+ }
+
+ /**
* Expands and selects the provided bubble as long as it already exists in the stack or the
* overflow. This is currently used when opening a bubble via clicking on a conversation widget.
*/
@@ -1730,7 +1750,8 @@
+ " expandedChanged=" + update.expandedChanged
+ " selectionChanged=" + update.selectionChanged
+ " suppressed=" + (update.suppressedBubble != null)
- + " unsuppressed=" + (update.unsuppressedBubble != null));
+ + " unsuppressed=" + (update.unsuppressedBubble != null)
+ + " shouldShowEducation=" + update.shouldShowEducation);
}
ensureBubbleViewsAndWindowCreated();
@@ -2155,6 +2176,12 @@
public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
mMainExecutor.execute(() -> mController.onBubbleDrag(bubbleKey, isBeingDragged));
}
+
+ @Override
+ public void showUserEducation(int positionX, int positionY) {
+ mMainExecutor.execute(() ->
+ mController.showUserEducation(new Point(positionX, positionY)));
+ }
}
private class BubblesImpl implements Bubbles {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index c6f74af..595a4af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -77,6 +77,7 @@
boolean orderChanged;
boolean suppressedSummaryChanged;
boolean expanded;
+ boolean shouldShowEducation;
@Nullable BubbleViewProvider selectedBubble;
@Nullable Bubble addedBubble;
@Nullable Bubble updatedBubble;
@@ -126,6 +127,7 @@
bubbleBarUpdate.expandedChanged = expandedChanged;
bubbleBarUpdate.expanded = expanded;
+ bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
if (selectionChanged) {
bubbleBarUpdate.selectedBubbleKey = selectedBubble != null
? selectedBubble.getKey()
@@ -165,6 +167,7 @@
*/
BubbleBarUpdate getInitialState() {
BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+ bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
for (int i = 0; i < bubbles.size(); i++) {
bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble());
}
@@ -187,6 +190,7 @@
private final Context mContext;
private final BubblePositioner mPositioner;
+ private final BubbleEducationController mEducationController;
private final Executor mMainExecutor;
/** Bubbles that are actively in the stack. */
private final List<Bubble> mBubbles;
@@ -233,10 +237,11 @@
private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
public BubbleData(Context context, BubbleLogger bubbleLogger, BubblePositioner positioner,
- Executor mainExecutor) {
+ BubbleEducationController educationController, Executor mainExecutor) {
mContext = context;
mLogger = bubbleLogger;
mPositioner = positioner;
+ mEducationController = educationController;
mMainExecutor = mainExecutor;
mOverflow = new BubbleOverflow(context, positioner);
mBubbles = new ArrayList<>();
@@ -447,6 +452,7 @@
if (bubble.shouldAutoExpand()) {
bubble.setShouldAutoExpand(false);
setSelectedBubbleInternal(bubble);
+
if (!mExpanded) {
setExpandedInternal(true);
}
@@ -877,6 +883,9 @@
private void dispatchPendingChanges() {
if (mListener != null && mStateChange.anythingChanged()) {
+ mStateChange.shouldShowEducation = mSelectedBubble != null
+ && mEducationController.shouldShowStackEducation(mSelectedBubble)
+ && !mExpanded;
mListener.applyUpdate(mStateChange);
}
mStateChange = new Update(mBubbles, mOverflowBubbles);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index 1c0e052..f56b171 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -44,7 +44,7 @@
static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
static final boolean DEBUG_EXPERIMENTS = true;
static final boolean DEBUG_OVERFLOW = false;
- static final boolean DEBUG_USER_EDUCATION = false;
+ public static final boolean DEBUG_USER_EDUCATION = false;
static final boolean DEBUG_POSITIONER = false;
public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 4dda068..5776ad1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -39,4 +39,6 @@
oneway void onBubbleDrag(in String key, in boolean isBeingDragged) = 7;
+ oneway void showUserEducation(in int positionX, in int positionY) = 8;
+
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 8f11253..e788341 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
@@ -36,6 +37,8 @@
import java.util.function.Consumer;
+import kotlin.Unit;
+
/**
* Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window
* manager to display bubbles. However, it is only used when bubbles are being displayed in
@@ -111,7 +114,7 @@
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
if (mExpandedView != null) {
- mEducationViewController.hideManageEducation(/* animated = */ false);
+ mEducationViewController.hideEducation(/* animated = */ false);
removeView(mExpandedView);
mExpandedView = null;
}
@@ -171,7 +174,9 @@
mExpandedView.setListener(new BubbleBarExpandedView.Listener() {
@Override
public void onTaskCreated() {
- mEducationViewController.maybeShowManageEducation(b, mExpandedView);
+ if (mEducationViewController != null && mExpandedView != null) {
+ mEducationViewController.maybeShowManageEducation(b, mExpandedView);
+ }
}
@Override
@@ -190,6 +195,10 @@
addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
}
+ if (mEducationViewController.isEducationVisible()) {
+ mEducationViewController.hideEducation(/* animated = */ true);
+ }
+
mIsExpanded = true;
mBubbleController.getSysuiProxy().onStackExpandChanged(true);
mAnimationHelper.animateExpansion(mExpandedBubble, () -> {
@@ -210,7 +219,7 @@
public void collapse() {
mIsExpanded = false;
final BubbleBarExpandedView viewToRemove = mExpandedView;
- mEducationViewController.hideManageEducation(/* animated = */ true);
+ mEducationViewController.hideEducation(/* animated = */ true);
mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
mBubbleController.getSysuiProxy().onStackExpandChanged(false);
mExpandedView = null;
@@ -218,6 +227,21 @@
showScrim(false);
}
+ /**
+ * Show bubble bar user education relative to the reference position.
+ * @param position the reference position in Screen coordinates.
+ */
+ public void showUserEducation(Point position) {
+ mEducationViewController.showStackEducation(position, /* root = */ this, () -> {
+ // When the user education is clicked hide it and expand the selected bubble
+ mEducationViewController.hideEducation(/* animated = */ true, () -> {
+ mBubbleController.expandStackWithSelectedBubble();
+ return Unit.INSTANCE;
+ });
+ return Unit.INSTANCE;
+ });
+ }
+
/** Sets the function to call to un-bubble the given conversation. */
public void setUnBubbleConversationCallback(
@Nullable Consumer<String> unBubbleConversationCallback) {
@@ -226,8 +250,8 @@
/** Hides the current modal education/menu view, expanded view or collapses the bubble stack */
private void hideMenuOrCollapse() {
- if (mEducationViewController.isManageEducationVisible()) {
- mEducationViewController.hideManageEducation(/* animated = */ true);
+ if (mEducationViewController.isEducationVisible()) {
+ mEducationViewController.hideEducation(/* animated = */ true);
} else if (isExpanded() && mExpandedView != null) {
mExpandedView.hideMenuOrCollapse();
} else {
@@ -275,7 +299,7 @@
*/
private void getTouchableRegion(Region outRegion) {
mTempRect.setEmpty();
- if (mIsExpanded) {
+ if (mIsExpanded || mEducationViewController.isEducationVisible()) {
getBoundsOnScreen(mTempRect);
outRegion.op(mTempRect, Region.Op.UNION);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
index 7b39c6f..ee552ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
@@ -15,23 +15,34 @@
*/
package com.android.wm.shell.bubbles.bar
+import android.annotation.LayoutRes
import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.Log
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.core.view.doOnLayout
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.wm.shell.R
import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_USER_EDUCATION
+import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES
+import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME
import com.android.wm.shell.bubbles.BubbleEducationController
import com.android.wm.shell.bubbles.BubbleViewProvider
import com.android.wm.shell.bubbles.setup
+import com.android.wm.shell.common.bubbles.BubblePopupDrawable
import com.android.wm.shell.common.bubbles.BubblePopupView
+import kotlin.math.roundToInt
/** Manages bubble education presentation and animation */
class BubbleEducationViewController(private val context: Context, private val listener: Listener) {
interface Listener {
- fun onManageEducationVisibilityChanged(isVisible: Boolean)
+ fun onEducationVisibilityChanged(isVisible: Boolean)
}
private var rootView: ViewGroup? = null
@@ -45,61 +56,112 @@
)
}
+ private val scrimView by lazy {
+ View(context).apply {
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ setOnClickListener { hideEducation(animated = true) }
+ }
+ }
+
private val controller by lazy { BubbleEducationController(context) }
/** Whether the education view is visible or being animated */
- val isManageEducationVisible: Boolean
+ val isEducationVisible: Boolean
get() = educationView != null && rootView != null
/**
+ * Hide the current education view if visible
+ *
+ * @param animated whether should hide with animation
+ */
+ @JvmOverloads
+ fun hideEducation(animated: Boolean, endActions: () -> Unit = {}) {
+ log { "hideEducation animated: $animated" }
+
+ if (animated) {
+ animateTransition(show = false) {
+ cleanUp()
+ endActions()
+ listener.onEducationVisibilityChanged(isVisible = false)
+ }
+ } else {
+ cleanUp()
+ endActions()
+ listener.onEducationVisibilityChanged(isVisible = false)
+ }
+ }
+
+ /**
+ * Show bubble bar stack user education.
+ *
+ * @param position the reference position for the user education in Screen coordinates.
+ * @param root the view to show user education in.
+ * @param educationClickHandler the on click handler for the user education view
+ */
+ fun showStackEducation(position: Point, root: ViewGroup, educationClickHandler: () -> Unit) {
+ hideEducation(animated = false)
+ log { "showStackEducation at: $position" }
+
+ educationView =
+ createEducationView(R.layout.bubble_bar_stack_education, root).apply {
+ setArrowDirection(BubblePopupDrawable.ArrowDirection.DOWN)
+ setArrowPosition(BubblePopupDrawable.ArrowPosition.End)
+ updateEducationPosition(view = this, position, root)
+ val arrowToEdgeOffset = popupDrawable?.config?.cornerRadius ?: 0f
+ doOnLayout {
+ it.pivotX = it.width - arrowToEdgeOffset
+ it.pivotY = it.height.toFloat()
+ }
+ setOnClickListener { educationClickHandler() }
+ }
+
+ rootView = root
+ animator = createAnimator()
+
+ root.addView(scrimView)
+ root.addView(educationView)
+ animateTransition(show = true) {
+ controller.hasSeenStackEducation = true
+ listener.onEducationVisibilityChanged(isVisible = true)
+ }
+ }
+
+ /**
* Show manage bubble education if hasn't been shown before
*
* @param bubble the bubble used for the manage education check
* @param root the view to show manage education in
*/
fun maybeShowManageEducation(bubble: BubbleViewProvider, root: ViewGroup) {
+ log { "maybeShowManageEducation bubble: $bubble" }
if (!controller.shouldShowManageEducation(bubble)) return
showManageEducation(root)
}
/**
- * Hide the manage education view if visible
- *
- * @param animated whether should hide with animation
- */
- fun hideManageEducation(animated: Boolean) {
- rootView?.let {
- fun cleanUp() {
- it.removeView(educationView)
- rootView = null
- listener.onManageEducationVisibilityChanged(isVisible = false)
- }
-
- if (animated) {
- animateTransition(show = false, ::cleanUp)
- } else {
- cleanUp()
- }
- }
- }
-
- /**
* Show manage education with animation
*
* @param root the view to show manage education in
*/
private fun showManageEducation(root: ViewGroup) {
- hideManageEducation(animated = false)
- if (educationView == null) {
- val eduView = createEducationView(root)
- educationView = eduView
- animator = createAnimation(eduView)
- }
- root.addView(educationView)
+ hideEducation(animated = false)
+ log { "showManageEducation" }
+
+ educationView =
+ createEducationView(R.layout.bubble_bar_manage_education, root).apply {
+ pivotY = 0f
+ doOnLayout { it.pivotX = it.width / 2f }
+ setOnClickListener { hideEducation(animated = true) }
+ }
+
rootView = root
+ animator = createAnimator()
+
+ root.addView(scrimView)
+ root.addView(educationView)
animateTransition(show = true) {
controller.hasSeenManageEducation = true
- listener.onManageEducationVisibilityChanged(isVisible = true)
+ listener.onEducationVisibilityChanged(isVisible = true)
}
}
@@ -110,39 +172,75 @@
* @param endActions a closure to be called when the animation completes
*/
private fun animateTransition(show: Boolean, endActions: () -> Unit) {
- animator?.let { animator ->
- animator
- .spring(DynamicAnimation.ALPHA, if (show) 1f else 0f)
- .spring(DynamicAnimation.SCALE_X, if (show) 1f else EDU_SCALE_HIDDEN)
- .spring(DynamicAnimation.SCALE_Y, if (show) 1f else EDU_SCALE_HIDDEN)
- .withEndActions(endActions)
- .start()
- } ?: endActions()
+ animator
+ ?.spring(DynamicAnimation.ALPHA, if (show) 1f else 0f)
+ ?.spring(DynamicAnimation.SCALE_X, if (show) 1f else EDU_SCALE_HIDDEN)
+ ?.spring(DynamicAnimation.SCALE_Y, if (show) 1f else EDU_SCALE_HIDDEN)
+ ?.withEndActions(endActions)
+ ?.start()
+ ?: endActions()
}
- private fun createEducationView(root: ViewGroup): BubblePopupView {
- val view =
- LayoutInflater.from(context).inflate(R.layout.bubble_bar_manage_education, root, false)
- as BubblePopupView
+ /** Remove education view from the root and clean up all relative properties */
+ private fun cleanUp() {
+ log { "cleanUp" }
+ rootView?.removeView(educationView)
+ rootView?.removeView(scrimView)
+ educationView = null
+ rootView = null
+ animator = null
+ }
- return view.apply {
- setup()
- alpha = 0f
- pivotY = 0f
- scaleX = EDU_SCALE_HIDDEN
- scaleY = EDU_SCALE_HIDDEN
- doOnLayout { it.pivotX = it.width / 2f }
- setOnClickListener { hideManageEducation(animated = true) }
+ /**
+ * Create education view by inflating layout provided.
+ *
+ * @param layout layout resource id to inflate. The root view should be [BubblePopupView]
+ * @param root view group to use as root for inflation, is not attached to root
+ */
+ private fun createEducationView(@LayoutRes layout: Int, root: ViewGroup): BubblePopupView {
+ val view = LayoutInflater.from(context).inflate(layout, root, false) as BubblePopupView
+ view.setup()
+ view.alpha = 0f
+ view.scaleX = EDU_SCALE_HIDDEN
+ view.scaleY = EDU_SCALE_HIDDEN
+ return view
+ }
+
+ /** Create animator for the user education transitions */
+ private fun createAnimator(): PhysicsAnimator<BubblePopupView>? {
+ return educationView?.let {
+ PhysicsAnimator.getInstance(it).apply { setDefaultSpringConfig(springConfig) }
}
}
- private fun createAnimation(view: BubblePopupView): PhysicsAnimator<BubblePopupView> {
- val animator = PhysicsAnimator.getInstance(view)
- animator.setDefaultSpringConfig(springConfig)
- return animator
+ /**
+ * Update user education view position relative to the reference position
+ *
+ * @param view the user education view to layout
+ * @param position the reference position in Screen coordinates
+ * @param root the root view to use for the layout
+ */
+ private fun updateEducationPosition(view: BubblePopupView, position: Point, root: ViewGroup) {
+ val rootBounds = Rect()
+ // Get root bounds on screen as position is in screen coordinates
+ root.getBoundsOnScreen(rootBounds)
+ // Get the offset to the arrow from the edge of the education view
+ val arrowToEdgeOffset =
+ view.popupDrawable?.config?.let { it.cornerRadius + it.arrowWidth / 2f }?.roundToInt()
+ ?: 0
+ // Calculate education view margins
+ val params = view.layoutParams as FrameLayout.LayoutParams
+ params.bottomMargin = rootBounds.bottom - position.y
+ params.rightMargin = rootBounds.right - position.x - arrowToEdgeOffset
+ view.layoutParams = params
+ }
+
+ private fun log(msg: () -> String) {
+ if (DEBUG_USER_EDUCATION) Log.d(TAG, msg())
}
companion object {
+ private val TAG = if (TAG_WITH_CLASS_NAME) "BubbleEducationViewController" else TAG_BUBBLES
private const val EDU_SCALE_HIDDEN = 0.5f
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
index 8142347..fc627a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
@@ -35,6 +35,7 @@
public boolean expandedChanged;
public boolean expanded;
+ public boolean shouldShowEducation;
@Nullable
public String selectedBubbleKey;
@Nullable
@@ -61,6 +62,7 @@
public BubbleBarUpdate(Parcel parcel) {
expandedChanged = parcel.readBoolean();
expanded = parcel.readBoolean();
+ shouldShowEducation = parcel.readBoolean();
selectedBubbleKey = parcel.readString();
addedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(),
BubbleInfo.class);
@@ -95,6 +97,7 @@
return "BubbleBarUpdate{ expandedChanged=" + expandedChanged
+ " expanded=" + expanded
+ " selectedBubbleKey=" + selectedBubbleKey
+ + " shouldShowEducation=" + shouldShowEducation
+ " addedBubble=" + addedBubble
+ " updatedBubble=" + updatedBubble
+ " suppressedBubbleKey=" + suppressedBubbleKey
@@ -114,6 +117,7 @@
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeBoolean(expandedChanged);
parcel.writeBoolean(expanded);
+ parcel.writeBoolean(shouldShowEducation);
parcel.writeString(selectedBubbleKey);
parcel.writeParcelable(addedBubble, flags);
parcel.writeParcelable(updatedBubble, flags);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
index 1fd22d0a..887af17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
@@ -31,7 +31,7 @@
import kotlin.properties.Delegates
/** A drawable for the [BubblePopupView] that draws a popup background with a directional arrow */
-class BubblePopupDrawable(private val config: Config) : Drawable() {
+class BubblePopupDrawable(val config: Config) : Drawable() {
/** The direction of the arrow in the popup drawable */
enum class ArrowDirection {
UP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
index f8a4946..444fbf7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
@@ -29,7 +29,8 @@
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
- private var popupDrawable: BubblePopupDrawable? = null
+ var popupDrawable: BubblePopupDrawable? = null
+ private set
/**
* Sets up the popup drawable with the config provided. Required to remove dependency on local
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index 1901e0b..a5000fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -19,6 +19,14 @@
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_30_70;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_70_30;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_MINIMIZE;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_NONE;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
import android.content.Context;
import android.content.res.Configuration;
@@ -263,7 +271,7 @@
private SnapTarget snap(int position, boolean hardDismiss) {
if (shouldApplyFreeSnapMode(position)) {
- return new SnapTarget(position, position, SnapTarget.FLAG_NONE);
+ return new SnapTarget(position, position, SNAP_TO_NONE);
}
int minIndex = -1;
float minDistance = Float.MAX_VALUE;
@@ -291,8 +299,7 @@
if (dockedSide == DOCKED_RIGHT) {
startPos += mInsets.left;
}
- mTargets.add(new SnapTarget(startPos, startPos, SnapTarget.FLAG_DISMISS_START,
- 0.35f));
+ mTargets.add(new SnapTarget(startPos, startPos, SNAP_TO_START_AND_DISMISS, 0.35f));
switch (mSnapMode) {
case SNAP_MODE_16_9:
addRatio16_9Targets(isHorizontalDivision, dividerMax);
@@ -307,15 +314,15 @@
addMinimizedTarget(isHorizontalDivision, dockedSide);
break;
}
- mTargets.add(new SnapTarget(dividerMax, dividerMax, SnapTarget.FLAG_DISMISS_END, 0.35f));
+ mTargets.add(new SnapTarget(dividerMax, dividerMax, SNAP_TO_END_AND_DISMISS, 0.35f));
}
private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition,
int bottomPosition, int dividerMax) {
- maybeAddTarget(topPosition, topPosition - getStartInset());
+ maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_30_70);
addMiddleTarget(isHorizontalDivision);
maybeAddTarget(bottomPosition,
- dividerMax - getEndInset() - (bottomPosition + mDividerSize));
+ dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_70_30);
}
private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) {
@@ -349,16 +356,16 @@
* Adds a target at {@param position} but only if the area with size of {@param smallerSize}
* meets the minimal size requirement.
*/
- private void maybeAddTarget(int position, int smallerSize) {
+ private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapTo) {
if (smallerSize >= mMinimalSizeResizableTask) {
- mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+ mTargets.add(new SnapTarget(position, position, snapTo));
}
}
private void addMiddleTarget(boolean isHorizontalDivision) {
int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
mInsets, mDisplayWidth, mDisplayHeight, mDividerSize);
- mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+ mTargets.add(new SnapTarget(position, position, SNAP_TO_50_50));
}
private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) {
@@ -372,7 +379,7 @@
position = mDisplayWidth - position - mInsets.right - mDividerSize;
}
}
- mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+ mTargets.add(new SnapTarget(position, position, SNAP_TO_MINIMIZE));
}
public SnapTarget getMiddleTarget() {
@@ -435,14 +442,6 @@
* Represents a snap target for the divider.
*/
public static class SnapTarget {
- public static final int FLAG_NONE = 0;
-
- /** If the divider reaches this value, the left/top task should be dismissed. */
- public static final int FLAG_DISMISS_START = 1;
-
- /** If the divider reaches this value, the right/bottom task should be dismissed */
- public static final int FLAG_DISMISS_END = 2;
-
/** Position of this snap target. The right/bottom edge of the top/left task snaps here. */
public final int position;
@@ -452,7 +451,10 @@
*/
public final int taskPosition;
- public final int flag;
+ /**
+ * An int describing the placement of the divider in this snap target.
+ */
+ public final @SnapPosition int snapTo;
public boolean isMiddleTarget;
@@ -462,14 +464,15 @@
*/
private final float distanceMultiplier;
- public SnapTarget(int position, int taskPosition, int flag) {
- this(position, taskPosition, flag, 1f);
+ public SnapTarget(int position, int taskPosition, @SnapPosition int snapTo) {
+ this(position, taskPosition, snapTo, 1f);
}
- public SnapTarget(int position, int taskPosition, int flag, float distanceMultiplier) {
+ public SnapTarget(int position, int taskPosition, @SnapPosition int snapTo,
+ float distanceMultiplier) {
this.position = position;
this.taskPosition = taskPosition;
- this.flag = flag;
+ this.snapTo = snapTo;
this.distanceMultiplier = distanceMultiplier;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 755dba0..4af03fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -23,13 +23,12 @@
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
-
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
-import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
-import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -511,13 +510,13 @@
* target indicates dismissing split.
*/
public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
- switch (snapTarget.flag) {
- case FLAG_DISMISS_START:
+ switch (snapTarget.snapTo) {
+ case SNAP_TO_START_AND_DISMISS:
flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
- case FLAG_DISMISS_END:
+ case SNAP_TO_END_AND_DISMISS:
flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index be1b9b1..ff38b7e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -57,6 +57,38 @@
public @interface SplitPosition {
}
+ /** The divider doesn't snap to any target and is freely placeable. */
+ public static final int SNAP_TO_NONE = 0;
+
+ /** A snap target positioned near the screen edge for a minimized task */
+ public static final int SNAP_TO_MINIMIZE = 1;
+
+ /** If the divider reaches this value, the left/top task should be dismissed. */
+ public static final int SNAP_TO_START_AND_DISMISS = 2;
+
+ /** A snap target in the first half of the screen, where the split is roughly 30-70. */
+ public static final int SNAP_TO_30_70 = 3;
+
+ /** The 50-50 snap target */
+ public static final int SNAP_TO_50_50 = 4;
+
+ /** A snap target in the latter half of the screen, where the split is roughly 70-30. */
+ public static final int SNAP_TO_70_30 = 5;
+
+ /** If the divider reaches this value, the right/bottom task should be dismissed. */
+ public static final int SNAP_TO_END_AND_DISMISS = 6;
+
+ @IntDef(prefix = { "SNAP_TO_" }, value = {
+ SNAP_TO_NONE,
+ SNAP_TO_MINIMIZE,
+ SNAP_TO_START_AND_DISMISS,
+ SNAP_TO_30_70,
+ SNAP_TO_50_50,
+ SNAP_TO_70_30,
+ SNAP_TO_END_AND_DISMISS
+ })
+ public @interface SnapPosition {}
+
public static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
public static final int[] CONTROLLED_WINDOWING_MODES =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index e9f3e1a..fd23d14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -35,6 +35,7 @@
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
+import com.android.wm.shell.bubbles.BubbleEducationController;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.properties.ProdBubbleProperties;
@@ -134,11 +135,18 @@
@WMSingleton
@Provides
+ static BubbleEducationController provideBubbleEducationProvider(Context context) {
+ return new BubbleEducationController(context);
+ }
+
+ @WMSingleton
+ @Provides
static BubbleData provideBubbleData(Context context,
BubbleLogger logger,
BubblePositioner positioner,
+ BubbleEducationController educationController,
@ShellMainThread ShellExecutor mainExecutor) {
- return new BubbleData(context, logger, positioner, mainExecutor);
+ return new BubbleData(context, logger, positioner, educationController, mainExecutor);
}
// Note: Handler needed for LauncherApps.register
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index b918c83..40c519e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.desktopmode
-import android.R
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
@@ -27,7 +26,6 @@
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
-import android.content.res.TypedArray
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
@@ -44,6 +42,7 @@
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
+import com.android.internal.policy.ScreenDecorationsUtils
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
@@ -696,10 +695,7 @@
finishTransaction: SurfaceControl.Transaction
) {
// Add rounded corners to freeform windows
- val ta: TypedArray = context.obtainStyledAttributes(
- intArrayOf(R.attr.dialogCornerRadius))
- val cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat()
- ta.recycle()
+ val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
info.changes
.filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
.forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 87ceaa4..00f6a1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -19,10 +19,10 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
@@ -239,9 +239,14 @@
@Override
public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
- if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible()
- || DesktopModeStatus.isEnabled())) {
- return this;
+ if (mRecentsHandler != null) {
+ if (mSplitHandler.isSplitScreenVisible()) {
+ return this;
+ } else if (mDesktopTasksController != null
+ // Check on the default display. Recents/gesture nav is only available there
+ && mDesktopTasksController.getVisibleTaskCount(DEFAULT_DISPLAY) > 0) {
+ return this;
+ }
}
return null;
}
@@ -662,7 +667,6 @@
if (!consumed) {
return false;
}
- //Sync desktop mode state (proto 2)
if (mDesktopTasksController != null) {
mDesktopTasksController.syncSurfaceState(info, finishTransaction);
return true;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 4a55429..26c7394 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -111,6 +111,8 @@
@Mock
private BubbleLogger mBubbleLogger;
@Mock
+ private BubbleEducationController mEducationController;
+ @Mock
private ShellExecutor mMainExecutor;
@Captor
@@ -191,7 +193,7 @@
mPositioner = new TestableBubblePositioner(mContext,
mock(WindowManager.class));
- mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner,
+ mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, mEducationController,
mMainExecutor);
// Used by BubbleData to set lastAccessedTime
@@ -385,6 +387,65 @@
assertOverflowChangedTo(ImmutableList.of());
}
+ /**
+ * Verifies that the update shouldn't show the user education, if the education is not required
+ */
+ @Test
+ public void test_shouldNotShowEducation() {
+ // Setup
+ when(mEducationController.shouldShowStackEducation(any())).thenReturn(false);
+ mBubbleData.setListener(mListener);
+
+ // Test
+ mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */
+ true);
+
+ // Verify
+ verifyUpdateReceived();
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.shouldShowEducation).isFalse();
+ }
+
+ /**
+ * Verifies that the update should show the user education, if the education is required
+ */
+ @Test
+ public void test_shouldShowEducation() {
+ // Setup
+ when(mEducationController.shouldShowStackEducation(any())).thenReturn(true);
+ mBubbleData.setListener(mListener);
+
+ // Test
+ mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */
+ true);
+
+ // Verify
+ verifyUpdateReceived();
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.shouldShowEducation).isTrue();
+ }
+
+ /**
+ * Verifies that the update shouldn't show the user education, if the education is required but
+ * the bubble should auto-expand
+ */
+ @Test
+ public void test_shouldShowEducation_shouldAutoExpand() {
+ // Setup
+ when(mEducationController.shouldShowStackEducation(any())).thenReturn(true);
+ mBubbleData.setListener(mListener);
+ mBubbleA1.setShouldAutoExpand(true);
+
+ // Test
+ mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */
+ true);
+
+ // Verify
+ verifyUpdateReceived();
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.shouldShowEducation).isFalse();
+ }
+
// COLLAPSED / ADD
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index fe2da5d..ad84c7f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -18,9 +18,9 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -146,7 +146,7 @@
public void testSnapToDismissStart() {
// verify it callbacks properly when the snap target indicates dismissing split.
DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
- DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START);
+ SNAP_TO_START_AND_DISMISS);
mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
waitDividerFlingFinished();
@@ -158,7 +158,7 @@
public void testSnapToDismissEnd() {
// verify it callbacks properly when the snap target indicates dismissing split.
DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
- DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END);
+ SNAP_TO_END_AND_DISMISS);
mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
waitDividerFlingFinished();
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index f6ae169..746745a 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -62,13 +62,14 @@
// Regular JNI
static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr,
- jlong paintPtr, jint lbStyle, jint lbWordStyle, jint start, jint end,
- jboolean isRtl) {
+ jlong paintPtr, jint lbStyle, jint lbWordStyle, jboolean hyphenation,
+ jint start, jint end, jboolean isRtl) {
Paint* paint = toPaint(paintPtr);
const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
toBuilder(builderPtr)
- ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, isRtl);
+ ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, hyphenation,
+ isRtl);
}
// Regular JNI
@@ -159,7 +160,7 @@
static const JNINativeMethod gMTBuilderMethods[] = {
// MeasuredParagraphBuilder native functions.
{"nInitBuilder", "()J", (void*)nInitBuilder},
- {"nAddStyleRun", "(JJIIIIZ)V", (void*)nAddStyleRun},
+ {"nAddStyleRun", "(JJIIZIIZ)V", (void*)nAddStyleRun},
{"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun},
{"nBuildMeasuredText", "(JJ[CZZZZ)J", (void*)nBuildMeasuredText},
{"nFreeBuilder", "(J)V", (void*)nFreeBuilder},
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 6679f8f..e0f1f6e 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -18,6 +18,7 @@
#include <include/android/SkSurfaceAndroid.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/encode/SkPngEncoder.h>
#include <SkCanvas.h>
#include <SkColor.h>
#include <SkColorSpace.h>
@@ -440,6 +441,13 @@
procs.fTypefaceProc = [](SkTypeface* tf, void* ctx){
return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
};
+ procs.fImageProc = [](SkImage* img, void* ctx) -> sk_sp<SkData> {
+ GrDirectContext* dCtx = static_cast<GrDirectContext*>(ctx);
+ return SkPngEncoder::Encode(dCtx,
+ img,
+ SkPngEncoder::Options{});
+ };
+ procs.fImageCtx = mRenderThread.getGrContext();
auto data = picture->serialize(&procs);
savePictureAsync(data, mCapturedFile);
mCaptureSequence = 0;
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index d6921c8..8c63580 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -17,9 +17,11 @@
package android.media;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2;
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -370,14 +372,14 @@
}
/**
- * Registers callback to be invoked when the {@link RouteListingPreference} of the target
- * router changes.
+ * Registers the given callback to be invoked when the {@link RouteListingPreference} of the
+ * target router changes.
*
- * <p>Calls using a previously registered callback will overwrite the callback record.
+ * <p>Calls using a previously registered callback will overwrite the previous executor.
*
* @see #setRouteListingPreference(RouteListingPreference)
- * @hide
*/
+ @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
public void registerRouteListingPreferenceCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) {
@@ -393,9 +395,8 @@
/**
* Unregisters the given callback to not receive {@link RouteListingPreference} change events.
- *
- * @hide
*/
+ @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
public void unregisterRouteListingPreferenceCallback(
@NonNull RouteListingPreferenceCallback callback) {
Objects.requireNonNull(callback, "callback must not be null");
@@ -462,9 +463,12 @@
/**
* Returns the current {@link RouteListingPreference} of the target router.
*
+ * <p>If this instance was created using {@link #getInstance(Context, String)}, then it returns
+ * the last {@link RouteListingPreference} set by the process this router was created for.
+ *
* @see #setRouteListingPreference(RouteListingPreference)
- * @hide
*/
+ @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
@Nullable
public RouteListingPreference getRouteListingPreference() {
synchronized (mLock) {
@@ -1201,9 +1205,19 @@
public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {}
}
- /** @hide */
+ /** Callback for receiving events related to {@link RouteListingPreference}. */
+ @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
public abstract static class RouteListingPreferenceCallback {
- /** @hide */
+
+ @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
+ public RouteListingPreferenceCallback() {}
+
+ /**
+ * Called when the {@link RouteListingPreference} changes.
+ *
+ * @see #getRouteListingPreference
+ */
+ @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {}
}
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
new file mode 100644
index 0000000..17962ee
--- /dev/null
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.media.flags"
+
+flag {
+ namespace: "media_solutions"
+ name: "enable_rlp_callbacks_in_media_router2"
+ description: "Make RouteListingPreference getter and callbacks public in MediaRouter2."
+ bug: "281067101"
+}
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index fe26dc3..233aee2 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -50,3 +50,46 @@
proguard_compatibility: false,
},
}
+
+android_app {
+ name: "ClockworkCredentialManager",
+ defaults: ["platform_app_defaults"],
+ certificate: "platform",
+ manifest: "wear/AndroidManifest.xml",
+ srcs: ["wear/src/**/*.kt"],
+ resource_dirs: ["wear/res"],
+
+ dex_preopt: {
+ profile_guided: true,
+ profile: "wear/profile.txt.prof",
+ },
+
+ static_libs: [
+ "PlatformComposeCore",
+ "androidx.activity_activity-compose",
+ "androidx.appcompat_appcompat",
+ "androidx.compose.foundation_foundation",
+ "androidx.compose.foundation_foundation-layout",
+ "androidx.compose.material_material-icons-core",
+ "androidx.compose.material_material-icons-extended",
+ "androidx.compose.ui_ui",
+ "androidx.core_core-ktx",
+ "androidx.credentials_credentials",
+ "androidx.lifecycle_lifecycle-extensions",
+ "androidx.lifecycle_lifecycle-livedata",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
+ "androidx.lifecycle_lifecycle-viewmodel-compose",
+ "androidx.wear.compose_compose-foundation",
+ "androidx.wear.compose_compose-material",
+ "kotlinx-coroutines-core",
+ ],
+
+ platform_apis: true,
+ privileged: true,
+
+ kotlincflags: ["-Xjvm-default=all"],
+
+ optimize: {
+ proguard_compatibility: false,
+ },
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/LifecycleEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/LifecycleEvent.kt
index 1ede64d..145e44d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/logging/LifecycleEvent.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/LifecycleEvent.kt
@@ -15,8 +15,6 @@
*/
package com.android.credentialmanager.logging
-import com.android.internal.logging.UiEventLogger.UiEventEnum.RESERVE_NEW_UI_EVENT_ID
-
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
diff --git a/packages/CredentialManager/wear/AndroidManifest.xml b/packages/CredentialManager/wear/AndroidManifest.xml
new file mode 100644
index 0000000..001a56d
--- /dev/null
+++ b/packages/CredentialManager/wear/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.credentialmanager">
+
+ <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/>
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+ <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
+
+ <application
+ android:allowBackup="true"
+ android:dataExtractionRules="@xml/data_extraction_rules"
+ android:fullBackupContent="@xml/backup_rules"
+ android:label="@string/app_name"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.CredentialSelector">
+
+ <activity
+ android:name=".CredentialSelectorActivity"
+ android:exported="true"
+ android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR"
+ android:launchMode="singleTop"
+ android:label="@string/app_name"
+ android:excludeFromRecents="true"
+ android:theme="@style/Theme.CredentialSelector">
+ </activity>
+ </application>
+
+</manifest>
diff --git a/packages/CredentialManager/wear/profile.txt.prof b/packages/CredentialManager/wear/profile.txt.prof
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/CredentialManager/wear/profile.txt.prof
diff --git a/packages/CredentialManager/wear/res/values/strings.xml b/packages/CredentialManager/wear/res/values/strings.xml
new file mode 100644
index 0000000..10ea918
--- /dev/null
+++ b/packages/CredentialManager/wear/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- The name of this application. Credential Manager is a service that centralizes and provides
+ access to a user's credentials used to sign in to various apps. [CHAR LIMIT=80] -->
+ <string name="app_name">Credential Manager</string>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/res/values/themes.xml b/packages/CredentialManager/wear/res/values/themes.xml
new file mode 100644
index 0000000..22329e9f
--- /dev/null
+++ b/packages/CredentialManager/wear/res/values/themes.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources>
+ <style name="Theme.CredentialSelector" parent="@*android:style/ThemeOverlay.DeviceDefault.Accent.DayNight">
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowIsTranslucent">true</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/res/xml/backup_rules.xml b/packages/CredentialManager/wear/res/xml/backup_rules.xml
new file mode 100644
index 0000000..9b42d90
--- /dev/null
+++ b/packages/CredentialManager/wear/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Sample backup rules file; uncomment and customize as necessary.
+ See https://developer.android.com/guide/topics/data/autobackup
+ for details.
+ Note: This file is ignored for devices older that API 31
+ See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+ <!--
+ <include domain="sharedpref" path="."/>
+ <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/res/xml/data_extraction_rules.xml b/packages/CredentialManager/wear/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..c6c3bb0
--- /dev/null
+++ b/packages/CredentialManager/wear/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Sample data extraction rules file; uncomment and customize as necessary.
+ See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+ for details.
+-->
+<data-extraction-rules>
+ <cloud-backup>
+ <!-- TODO: Use <include> and <exclude> to control what is backed up.
+ <include .../>
+ <exclude .../>
+ -->
+ </cloud-backup>
+ <!--
+ <device-transfer>
+ <include .../>
+ <exclude .../>
+ </device-transfer>
+ -->
+</data-extraction-rules>
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
new file mode 100644
index 0000000..f7b2499
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.0N
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.activity.ComponentActivity
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material.Text
+
+class CredentialSelectorActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ MaterialTheme {
+ Text("Credential Manager entry point")
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 4a252a9..471f3b9 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -22,6 +22,7 @@
import com.android.settingslib.spa.framework.common.SpaEnvironment
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
+import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.dialog.AlertDialogPageProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsExposedDropdownMenuBoxPageProvider
@@ -32,7 +33,6 @@
import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsOutlinedTextFieldPageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
-import com.android.settingslib.spa.gallery.page.ChartPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/BarChartEntry.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/BarChartEntry.kt
new file mode 100644
index 0000000..bf7a8e1
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/BarChartEntry.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.chart
+
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.widget.chart.BarChart
+import com.android.settingslib.spa.widget.chart.BarChartData
+import com.android.settingslib.spa.widget.chart.BarChartModel
+import com.android.settingslib.spa.widget.chart.ColorPalette
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.github.mikephil.charting.formatter.IAxisValueFormatter
+
+fun createBarChartEntry(owner: SettingsPage) = SettingsEntryBuilder.create("Bar Chart", owner)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = "Bar Chart"
+ })
+ BarChart(
+ barChartModel = object : BarChartModel {
+ override val chartDataList = listOf(
+ BarChartData(x = 0f, y = listOf(12f, 2f)),
+ BarChartData(x = 1f, y = listOf(5f, 1f)),
+ BarChartData(x = 2f, y = listOf(21f, 2f)),
+ BarChartData(x = 3f, y = listOf(5f, 1f)),
+ BarChartData(x = 4f, y = listOf(10f, 0f)),
+ BarChartData(x = 5f, y = listOf(9f, 1f)),
+ BarChartData(x = 6f, y = listOf(1f, 1f)),
+ )
+ override val colors = listOf(ColorPalette.green, ColorPalette.yellow)
+ override val xValueFormatter = IAxisValueFormatter { value, _ ->
+ "4/${value.toInt() + 1}"
+ }
+ override val yValueFormatter = IAxisValueFormatter { value, _ ->
+ "${value.toInt()}m"
+ }
+ override val yAxisMaxValue = 30f
+ }
+ )
+ }.build()
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/chart/ChartPageProvider.kt
similarity index 73%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt
index 69c4705..7a6ae2c 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/chart/ChartPageProvider.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.gallery.page
+package com.android.settingslib.spa.gallery.chart
import android.os.Bundle
import androidx.compose.runtime.Composable
@@ -25,9 +25,6 @@
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.chart.BarChart
-import com.android.settingslib.spa.widget.chart.BarChartData
-import com.android.settingslib.spa.widget.chart.BarChartModel
import com.android.settingslib.spa.widget.chart.LineChart
import com.android.settingslib.spa.widget.chart.LineChartData
import com.android.settingslib.spa.widget.chart.LineChartModel
@@ -83,36 +80,7 @@
)
}.build()
)
- entryList.add(
- SettingsEntryBuilder.create("Bar Chart", owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = "Bar Chart"
- })
- BarChart(
- barChartModel = object : BarChartModel {
- override val chartDataList = listOf(
- BarChartData(x = 0f, y = 12f),
- BarChartData(x = 1f, y = 5f),
- BarChartData(x = 2f, y = 21f),
- BarChartData(x = 3f, y = 5f),
- BarChartData(x = 4f, y = 10f),
- BarChartData(x = 5f, y = 9f),
- BarChartData(x = 6f, y = 1f),
- )
- override val xValueFormatter =
- IAxisValueFormatter { value, _ ->
- "${WeekDay.values()[value.toInt()]}"
- }
- override val yValueFormatter =
- IAxisValueFormatter { value, _ ->
- "${value.toInt()}m"
- }
- override val yAxisMaxValue = 30f
- }
- )
- }.build()
- )
+ entryList.add(createBarChartEntry(owner))
entryList.add(
SettingsEntryBuilder.create("Pie Chart", owner)
.setUiLayoutFn {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index bb311a5..6cac220 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -28,12 +28,12 @@
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
+import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.dialog.AlertDialogPageProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageModel
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
-import com.android.settingslib.spa.gallery.page.ChartPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
index 27d270c..e6decb1 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.screenshot
+import androidx.compose.material3.MaterialTheme
import com.android.settingslib.spa.widget.chart.BarChart
import com.android.settingslib.spa.widget.chart.BarChartData
import com.android.settingslib.spa.widget.chart.BarChartModel
@@ -45,17 +46,19 @@
@Test
fun test() {
screenshotRule.screenshotTest("barChart") {
+ val color = MaterialTheme.colorScheme.surfaceVariant
BarChart(
barChartModel = object : BarChartModel {
override val chartDataList = listOf(
- BarChartData(x = 0f, y = 12f),
- BarChartData(x = 1f, y = 5f),
- BarChartData(x = 2f, y = 21f),
- BarChartData(x = 3f, y = 5f),
- BarChartData(x = 4f, y = 10f),
- BarChartData(x = 5f, y = 9f),
- BarChartData(x = 6f, y = 1f),
+ BarChartData(x = 0f, y = listOf(12f)),
+ BarChartData(x = 1f, y = listOf(5f)),
+ BarChartData(x = 2f, y = listOf(21f)),
+ BarChartData(x = 3f, y = listOf(5f)),
+ BarChartData(x = 4f, y = listOf(10f)),
+ BarChartData(x = 5f, y = listOf(9f)),
+ BarChartData(x = 6f, y = listOf(1f)),
)
+ override val colors = listOf(color)
override val xValueFormatter =
IAxisValueFormatter { value, _ ->
"${WeekDay.values()[value.toInt()]}"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
index 0b0f07e..7ca15d9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
@@ -31,6 +31,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
@@ -52,6 +53,11 @@
val chartDataList: List<BarChartData>
/**
+ * The color list for [BarChart].
+ */
+ val colors: List<Color>
+
+ /**
* The label text formatter for x value.
*/
val xValueFormatter: IAxisValueFormatter?
@@ -83,34 +89,14 @@
}
data class BarChartData(
- var x: Float?,
- var y: Float?,
+ var x: Float,
+ var y: List<Float>,
)
@Composable
fun BarChart(barChartModel: BarChartModel) {
- BarChart(
- chartDataList = barChartModel.chartDataList,
- xValueFormatter = barChartModel.xValueFormatter,
- yValueFormatter = barChartModel.yValueFormatter,
- yAxisMinValue = barChartModel.yAxisMinValue,
- yAxisMaxValue = barChartModel.yAxisMaxValue,
- yAxisLabelCount = barChartModel.yAxisLabelCount,
- )
-}
-
-@Composable
-fun BarChart(
- chartDataList: List<BarChartData>,
- modifier: Modifier = Modifier,
- xValueFormatter: IAxisValueFormatter? = null,
- yValueFormatter: IAxisValueFormatter? = null,
- yAxisMinValue: Float = 0f,
- yAxisMaxValue: Float = 30f,
- yAxisLabelCount: Int = 4,
-) {
Column(
- modifier = modifier
+ modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
horizontalAlignment = Alignment.CenterHorizontally,
@@ -126,50 +112,61 @@
val colorScheme = MaterialTheme.colorScheme
val labelTextColor = colorScheme.onSurfaceVariant.toArgb()
val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value
- Crossfade(targetState = chartDataList) { barChartData ->
- AndroidView(factory = { context ->
- BarChart(context).apply {
- // Fixed Settings.
- layoutParams = LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT,
- )
- this.description.isEnabled = false
- this.legend.isEnabled = false
- this.extraBottomOffset = 4f
- this.setScaleEnabled(false)
+ Crossfade(
+ targetState = barChartModel.chartDataList,
+ label = "chartDataList",
+ ) { barChartData ->
+ AndroidView(
+ factory = { context ->
+ BarChart(context).apply {
+ layoutParams = LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ )
+ description.isEnabled = false
+ legend.isEnabled = false
+ extraBottomOffset = 4f
+ setScaleEnabled(false)
- this.xAxis.position = XAxis.XAxisPosition.BOTTOM
- this.xAxis.setDrawGridLines(false)
- this.xAxis.setDrawAxisLine(false)
- this.xAxis.textColor = labelTextColor
- this.xAxis.textSize = labelTextSize
- this.xAxis.yOffset = 10f
+ xAxis.apply {
+ position = XAxis.XAxisPosition.BOTTOM
+ setDrawAxisLine(false)
+ setDrawGridLines(false)
+ textColor = labelTextColor
+ textSize = labelTextSize
+ valueFormatter = barChartModel.xValueFormatter
+ yOffset = 10f
+ }
- this.axisLeft.isEnabled = false
- this.axisRight.setDrawAxisLine(false)
- this.axisRight.textSize = labelTextSize
- this.axisRight.textColor = labelTextColor
- this.axisRight.gridColor = colorScheme.divider.toArgb()
- this.axisRight.xOffset = 10f
+ axisLeft.apply {
+ axisMaximum = barChartModel.yAxisMaxValue
+ axisMinimum = barChartModel.yAxisMinValue
+ isEnabled = false
+ }
- // Customizable Settings.
- this.xAxis.valueFormatter = xValueFormatter
- this.axisRight.valueFormatter = yValueFormatter
-
- this.axisLeft.axisMinimum = yAxisMinValue
- this.axisLeft.axisMaximum = yAxisMaxValue
- this.axisRight.axisMinimum = yAxisMinValue
- this.axisRight.axisMaximum = yAxisMaxValue
-
- this.axisRight.setLabelCount(yAxisLabelCount, true)
- }
- },
+ axisRight.apply {
+ axisMaximum = barChartModel.yAxisMaxValue
+ axisMinimum = barChartModel.yAxisMinValue
+ gridColor = colorScheme.divider.toArgb()
+ setDrawAxisLine(false)
+ setLabelCount(barChartModel.yAxisLabelCount, true)
+ textColor = labelTextColor
+ textSize = labelTextSize
+ valueFormatter = barChartModel.yValueFormatter
+ xOffset = 10f
+ }
+ }
+ },
modifier = Modifier
.wrapContentSize()
.padding(4.dp),
- update = {
- updateBarChartWithData(it, barChartData, colorScheme)
+ update = { barChart ->
+ updateBarChartWithData(
+ chart = barChart,
+ data = barChartData,
+ colorList = barChartModel.colors,
+ colorScheme = colorScheme,
+ )
}
)
}
@@ -177,26 +174,25 @@
}
}
-fun updateBarChartWithData(
+private fun updateBarChartWithData(
chart: BarChart,
data: List<BarChartData>,
+ colorList: List<Color>,
colorScheme: ColorScheme
) {
- val entries = ArrayList<BarEntry>()
- for (i in data.indices) {
- val item = data[i]
- entries.add(BarEntry(item.x ?: 0.toFloat(), item.y ?: 0.toFloat()))
+ val entries = data.map { item ->
+ BarEntry(item.x, item.y.toFloatArray())
}
- val ds = BarDataSet(entries, "")
- ds.colors = arrayListOf(colorScheme.surfaceVariant.toArgb())
- ds.setDrawValues(false)
- ds.isHighlightEnabled = true
- ds.highLightColor = colorScheme.primary.toArgb()
- ds.highLightAlpha = 255
+ val ds = BarDataSet(entries, "").apply {
+ colors = colorList.map(Color::toArgb)
+ setDrawValues(false)
+ isHighlightEnabled = true
+ highLightColor = colorScheme.primary.toArgb()
+ highLightAlpha = 255
+ }
// TODO: Sets round corners for bars.
- val d = BarData(ds)
- chart.data = d
+ chart.data = BarData(ds)
chart.invalidate()
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
index d7bbf08..ec43aab 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
@@ -18,20 +18,22 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.android.settingslib.spa.framework.theme.SettingsDimension
-import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
@Composable
@@ -48,6 +50,7 @@
expanded = expanded,
onExpandedChange = { expanded = it },
modifier = Modifier
+ .width(350.dp)
.padding(SettingsDimension.itemPadding),
) {
OutlinedTextField(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
index 6b3ae77..459a783 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
@@ -59,16 +59,16 @@
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = it },
- modifier = Modifier.width(350.dp).padding(
- horizontal = SettingsDimension.itemPaddingEnd,
- vertical = SettingsDimension.itemPaddingVertical
- ).onSizeChanged {
- dropDownWidth = it.width
- },
+ modifier = Modifier
+ .width(350.dp)
+ .padding(SettingsDimension.itemPadding)
+ .onSizeChanged { dropDownWidth = it.width },
) {
OutlinedTextField(
// The `menuAnchor` modifier must be passed to the text field for correctness.
- modifier = Modifier.menuAnchor().fillMaxWidth(),
+ modifier = Modifier
+ .menuAnchor()
+ .fillMaxWidth(),
value = selectedOptionsState.joinToString(", "),
onValueChange = onselectedOptionStateChange,
label = { Text(text = label) },
@@ -83,12 +83,15 @@
if (options.isNotEmpty()) {
ExposedDropdownMenu(
expanded = expanded,
- modifier = Modifier.fillMaxWidth()
+ modifier = Modifier
+ .fillMaxWidth()
.width(with(LocalDensity.current) { dropDownWidth.toDp() }),
onDismissRequest = { expanded = false },
) {
options.forEach { option ->
- TextButton(modifier = Modifier.fillMaxHeight().fillMaxWidth(), onClick = {
+ TextButton(modifier = Modifier
+ .fillMaxHeight()
+ .fillMaxWidth(), onClick = {
if (selectedOptionsState.contains(option)) {
selectedOptionsState.remove(
option
@@ -100,7 +103,9 @@
}
}) {
Row(
- modifier = Modifier.fillMaxHeight().fillMaxWidth(),
+ modifier = Modifier
+ .fillMaxHeight()
+ .fillMaxWidth(),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt
index 2230d6c..0fe755f 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt
@@ -17,13 +17,17 @@
package com.android.settingslib.spa.widget.chart
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.assertContainsColor
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -61,22 +65,21 @@
@Test
fun bar_chart_displayed() {
composeTestRule.setContent {
- BarChart(
- chartDataList = listOf(
- BarChartData(x = 0f, y = 12f),
- BarChartData(x = 1f, y = 5f),
- BarChartData(x = 2f, y = 21f),
- BarChartData(x = 3f, y = 5f),
- BarChartData(x = 4f, y = 10f),
- BarChartData(x = 5f, y = 9f),
- BarChartData(x = 6f, y = 1f),
- ),
- yAxisMaxValue = 30f,
- modifier = Modifier.semantics { chart = "bar" }
- )
+ BarChart(object : BarChartModel {
+ override val chartDataList = listOf(
+ BarChartData(x = 0f, y = listOf(12f)),
+ BarChartData(x = 1f, y = listOf(5f)),
+ BarChartData(x = 2f, y = listOf(21f)),
+ BarChartData(x = 3f, y = listOf(5f)),
+ BarChartData(x = 4f, y = listOf(10f)),
+ BarChartData(x = 5f, y = listOf(9f)),
+ BarChartData(x = 6f, y = listOf(1f)),
+ )
+ override val colors = listOf(Color.Blue)
+ })
}
- composeTestRule.onNode(hasChart("bar")).assertIsDisplayed()
+ composeTestRule.onRoot().captureToImage().assertContainsColor(Color.Blue)
}
@Test
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ImageAssertions.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ImageAssertions.kt
new file mode 100644
index 0000000..0190989
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ImageAssertions.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.testutils
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.toPixelMap
+
+/**
+ * Asserts that the expected color is present in this bitmap.
+ *
+ * @throws AssertionError if the expected color is not present.
+ */
+fun ImageBitmap.assertContainsColor(expectedColor: Color) {
+ assert(containsColor(expectedColor)) {
+ "The given color $expectedColor was not found in the bitmap."
+ }
+}
+
+private fun ImageBitmap.containsColor(expectedColor: Color): Boolean {
+ val pixels = toPixelMap()
+ for (x in 0 until width) {
+ for (y in 0 until height) {
+ if (pixels[x, y] == expectedColor) return true
+ }
+ }
+ return false
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 9d533fa..412a342 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -549,7 +549,12 @@
/**
* Return the combined service state.
- * To make behavior consistent with SystemUI and Settings/AboutPhone/SIM status UI
+ * To make behavior consistent with SystemUI and Settings/AboutPhone/SIM status UI.
+ *
+ * This method returns a single service state int if either the voice reg state is
+ * {@link ServiceState#STATE_IN_SERVICE} or if data network is registered via a
+ * WWAN transport type. We consider the combined service state of an IWLAN network
+ * to be OOS.
*
* @param serviceState Service state. {@link ServiceState}
*/
@@ -558,23 +563,37 @@
return ServiceState.STATE_OUT_OF_SERVICE;
}
- // Consider the device to be in service if either voice or data
- // service is available. Some SIM cards are marketed as data-only
- // and do not support voice service, and on these SIM cards, we
- // want to show signal bars for data service as well as the "no
- // service" or "emergency calls only" text that indicates that voice
- // is not available. Note that we ignore the IWLAN service state
- // because that state indicates the use of VoWIFI and not cell service
- final int state = serviceState.getState();
- final int dataState = serviceState.getDataRegistrationState();
+ final int voiceRegState = serviceState.getVoiceRegState();
- if (state == ServiceState.STATE_OUT_OF_SERVICE
- || state == ServiceState.STATE_EMERGENCY_ONLY) {
- if (dataState == ServiceState.STATE_IN_SERVICE && isNotInIwlan(serviceState)) {
+ // Consider a mobile connection to be "in service" if either voice is IN_SERVICE
+ // or the data registration reports IN_SERVICE on a transport type of WWAN. This
+ // effectively excludes the IWLAN condition. IWLAN connections imply service via
+ // Wi-Fi rather than cellular, and so we do not consider these transports when
+ // determining if cellular is "in service".
+
+ if (voiceRegState == ServiceState.STATE_OUT_OF_SERVICE
+ || voiceRegState == ServiceState.STATE_EMERGENCY_ONLY) {
+ if (isDataRegInWwanAndInService(serviceState)) {
return ServiceState.STATE_IN_SERVICE;
}
}
- return state;
+
+ return voiceRegState;
+ }
+
+ // ServiceState#mDataRegState can be set to IN_SERVICE if the network is registered
+ // on either a WLAN or WWAN network. Since we want to exclude the WLAN network, we can
+ // query the WWAN network directly and check for its registration state
+ private static boolean isDataRegInWwanAndInService(ServiceState serviceState) {
+ final NetworkRegistrationInfo networkRegWwan = serviceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+ if (networkRegWwan == null) {
+ return false;
+ }
+
+ return networkRegWwan.isInService();
}
/** Get the corresponding adaptive icon drawable. */
@@ -598,21 +617,6 @@
UserHandle.getUserHandleForUid(appInfo.uid));
}
- private static boolean isNotInIwlan(ServiceState serviceState) {
- final NetworkRegistrationInfo networkRegWlan = serviceState.getNetworkRegistrationInfo(
- NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
- if (networkRegWlan == null) {
- return true;
- }
-
- final boolean isInIwlan = (networkRegWlan.getRegistrationState()
- == NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
- || (networkRegWlan.getRegistrationState()
- == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
- return !isInIwlan;
- }
-
/**
* Returns a bitmap with rounded corner.
*
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 755d971..fa8c1fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -4,9 +4,9 @@
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothCsipSetCoordinator;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -14,6 +14,7 @@
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
import android.net.Uri;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
@@ -23,6 +24,7 @@
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
import androidx.core.graphics.drawable.IconCompat;
import com.android.settingslib.R;
@@ -470,6 +472,136 @@
return extraTagValue(KEY_HEARABLE_CONTROL_SLICE, data);
}
+ /**
+ * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means:
+ * 1) currently connected
+ * 2) is Hearing Aid or LE Audio
+ * OR
+ * 3) connected profile matches currentAudioProfile
+ *
+ * @param cachedDevice the CachedBluetoothDevice
+ * @param audioManager audio manager to get the current audio profile
+ * @return if the device is AvailableMediaBluetoothDevice
+ */
+ @WorkerThread
+ public static boolean isAvailableMediaBluetoothDevice(
+ CachedBluetoothDevice cachedDevice, AudioManager audioManager) {
+ int audioMode = audioManager.getMode();
+ int currentAudioProfile;
+
+ if (audioMode == AudioManager.MODE_RINGTONE
+ || audioMode == AudioManager.MODE_IN_CALL
+ || audioMode == AudioManager.MODE_IN_COMMUNICATION) {
+ // in phone call
+ currentAudioProfile = BluetoothProfile.HEADSET;
+ } else {
+ // without phone call
+ currentAudioProfile = BluetoothProfile.A2DP;
+ }
+
+ boolean isFilterMatched = false;
+ if (isDeviceConnected(cachedDevice)) {
+ // If device is Hearing Aid or LE Audio, it is compatible with HFP and A2DP.
+ // It would show in Available Devices group.
+ if (cachedDevice.isConnectedAshaHearingAidDevice()
+ || cachedDevice.isConnectedLeAudioDevice()) {
+ Log.d(TAG, "isFilterMatched() device : "
+ + cachedDevice.getName() + ", the profile is connected.");
+ return true;
+ }
+ // According to the current audio profile type,
+ // this page will show the bluetooth device that have corresponding profile.
+ // For example:
+ // If current audio profile is a2dp, show the bluetooth device that have a2dp profile.
+ // If current audio profile is headset,
+ // show the bluetooth device that have headset profile.
+ switch (currentAudioProfile) {
+ case BluetoothProfile.A2DP:
+ isFilterMatched = cachedDevice.isConnectedA2dpDevice();
+ break;
+ case BluetoothProfile.HEADSET:
+ isFilterMatched = cachedDevice.isConnectedHfpDevice();
+ break;
+ }
+ }
+ return isFilterMatched;
+ }
+
+ /**
+ * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means:
+ * 1) currently connected
+ * 2) is not Hearing Aid or LE Audio
+ * AND
+ * 3) connected profile does not match currentAudioProfile
+ *
+ * @param cachedDevice the CachedBluetoothDevice
+ * @param audioManager audio manager to get the current audio profile
+ * @return if the device is AvailableMediaBluetoothDevice
+ */
+ @WorkerThread
+ public static boolean isConnectedBluetoothDevice(
+ CachedBluetoothDevice cachedDevice, AudioManager audioManager) {
+ int audioMode = audioManager.getMode();
+ int currentAudioProfile;
+
+ if (audioMode == AudioManager.MODE_RINGTONE
+ || audioMode == AudioManager.MODE_IN_CALL
+ || audioMode == AudioManager.MODE_IN_COMMUNICATION) {
+ // in phone call
+ currentAudioProfile = BluetoothProfile.HEADSET;
+ } else {
+ // without phone call
+ currentAudioProfile = BluetoothProfile.A2DP;
+ }
+
+ boolean isFilterMatched = false;
+ if (isDeviceConnected(cachedDevice)) {
+ // If device is Hearing Aid or LE Audio, it is compatible with HFP and A2DP.
+ // It would not show in Connected Devices group.
+ if (cachedDevice.isConnectedAshaHearingAidDevice()
+ || cachedDevice.isConnectedLeAudioDevice()) {
+ return false;
+ }
+ // According to the current audio profile type,
+ // this page will show the bluetooth device that doesn't have corresponding profile.
+ // For example:
+ // If current audio profile is a2dp,
+ // show the bluetooth device that doesn't have a2dp profile.
+ // If current audio profile is headset,
+ // show the bluetooth device that doesn't have headset profile.
+ switch (currentAudioProfile) {
+ case BluetoothProfile.A2DP:
+ isFilterMatched = !cachedDevice.isConnectedA2dpDevice();
+ break;
+ case BluetoothProfile.HEADSET:
+ isFilterMatched = !cachedDevice.isConnectedHfpDevice();
+ break;
+ }
+ }
+ return isFilterMatched;
+ }
+
+ /**
+ * Check if the Bluetooth device is an active media device
+ *
+ * @param cachedDevice the CachedBluetoothDevice
+ * @return if the Bluetooth device is an active media device
+ */
+ public static boolean isActiveMediaDevice(CachedBluetoothDevice cachedDevice) {
+ return cachedDevice.isActiveDevice(BluetoothProfile.A2DP)
+ || cachedDevice.isActiveDevice(BluetoothProfile.HEADSET)
+ || cachedDevice.isActiveDevice(BluetoothProfile.HEARING_AID)
+ || cachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO);
+ }
+
+ private static boolean isDeviceConnected(CachedBluetoothDevice cachedDevice) {
+ if (cachedDevice == null) {
+ return false;
+ }
+ final BluetoothDevice device = cachedDevice.getDevice();
+ return device.getBondState() == BluetoothDevice.BOND_BONDED && device.isConnected();
+ }
+
@SuppressLint("NewApi") // Hidden API made public
private static boolean doesClassMatch(BluetoothClass btClass, int classId) {
return btClass.doesClassMatch(classId);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 64c271b..c67df71 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -54,6 +54,7 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Stream;
/**
* CachedBluetoothDevice represents a remote Bluetooth device. It contains
@@ -684,6 +685,20 @@
return mDevice.getBatteryLevel();
}
+ /**
+ * Get the lowest battery level from remote device and its member devices
+ * @return battery level in percentage [0-100] or
+ * {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+ */
+ public int getMinBatteryLevelWithMemberDevices() {
+ return Stream.concat(Stream.of(this), mMemberDevices.stream())
+ .mapToInt(cachedDevice -> cachedDevice.getBatteryLevel())
+ .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+ .min()
+ .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ }
+
+
void refresh() {
ThreadUtils.postOnBackgroundThread(() -> {
if (BluetoothUtils.isAdvancedDetailsHeader(mDevice)) {
@@ -1185,7 +1200,7 @@
// BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF, or BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
// any other value should be a framework bug. Thus assume here that if value is greater
// than BluetoothDevice.BATTERY_LEVEL_UNKNOWN, it must be valid
- final int batteryLevel = getBatteryLevel();
+ final int batteryLevel = getMinBatteryLevelWithMemberDevices();
if (batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
// TODO: name com.android.settingslib.bluetooth.Utils something different
batteryLevelPercentageString =
@@ -1360,7 +1375,7 @@
// BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF, or BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
// any other value should be a framework bug. Thus assume here that if value is greater
// than BluetoothDevice.BATTERY_LEVEL_UNKNOWN, it must be valid
- final int batteryLevel = getBatteryLevel();
+ final int batteryLevel = getMinBatteryLevelWithMemberDevices();
if (batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
// TODO: name com.android.settingslib.bluetooth.Utils something different
batteryLevelPercentageString =
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
index ad9886e..88dcc0d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
@@ -22,6 +22,9 @@
public final class BatteryUtils {
+ /** The key to get the time to full from Settings.Global */
+ public static final String GLOBAL_TIME_TO_FULL_MILLIS = "time_to_full_millis";
+
/** Gets the latest sticky battery intent from the Android system. */
public static Intent getBatteryIntent(Context context) {
return context.registerReceiver(
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index bb72375..29846ac 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -200,47 +200,55 @@
@Test
public void isInService_voiceInService_returnTrue() {
- when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
assertThat(Utils.isInService(mServiceState)).isTrue();
}
@Test
public void isInService_voiceOutOfServiceDataInService_returnTrue() {
- when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+ when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
- when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
- NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
assertThat(Utils.isInService(mServiceState)).isTrue();
}
@Test
public void isInService_voiceOutOfServiceDataInServiceOnIwLan_returnFalse() {
- when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+ when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
- when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
- NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
+
+ assertThat(Utils.isInService(mServiceState)).isFalse();
+ }
+
+ @Test
+ public void isInService_voiceOutOfServiceDataNull_returnFalse() {
+ when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+ when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(null);
assertThat(Utils.isInService(mServiceState)).isFalse();
}
@Test
public void isInService_voiceOutOfServiceDataOutOfService_returnFalse() {
- when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getDataRegistrationState()).thenReturn(
- ServiceState.STATE_OUT_OF_SERVICE);
+ when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+ when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.isInService()).thenReturn(false);
assertThat(Utils.isInService(mServiceState)).isFalse();
}
@Test
public void isInService_ServiceStatePowerOff_returnFalse() {
- when(mServiceState.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
+ when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_POWER_OFF);
assertThat(Utils.isInService(mServiceState)).isFalse();
}
@@ -253,7 +261,7 @@
@Test
public void getCombinedServiceState_ServiceStatePowerOff_returnPowerOff() {
- when(mServiceState.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
+ when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_POWER_OFF);
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_POWER_OFF);
@@ -261,7 +269,7 @@
@Test
public void getCombinedServiceState_voiceInService_returnInService() {
- when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_IN_SERVICE);
@@ -269,12 +277,10 @@
@Test
public void getCombinedServiceState_voiceOutOfServiceDataInService_returnInService() {
- when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
- AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
- when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
- NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_IN_SERVICE);
@@ -282,12 +288,10 @@
@Test
public void getCombinedServiceState_voiceOutOfServiceDataInServiceOnIwLan_returnOutOfService() {
- when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
- when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
- NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+ when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_OUT_OF_SERVICE);
@@ -295,7 +299,7 @@
@Test
public void getCombinedServiceState_voiceOutOfServiceDataOutOfService_returnOutOfService() {
- when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+ when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegistrationState()).thenReturn(
ServiceState.STATE_OUT_OF_SERVICE);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 83972e8..7409eea 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -16,7 +16,6 @@
package com.android.settingslib.bluetooth;
import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -25,6 +24,7 @@
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
import android.net.Uri;
import android.util.Pair;
@@ -46,6 +46,8 @@
private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock
private BluetoothDevice mBluetoothDevice;
+ @Mock
+ private AudioManager mAudioManager;
private Context mContext;
private static final String STRING_METADATA = "string_metadata";
@@ -255,4 +257,110 @@
public void isAdvancedUntetheredDevice_noMetadata_returnFalse() {
assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
}
+
+ @Test
+ public void isAvailableMediaBluetoothDevice_isConnectedLeAudioDevice_returnTrue() {
+ when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+ assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
+ mAudioManager)).isEqualTo(true);
+ }
+
+ @Test
+ public void isAvailableMediaBluetoothDevice_isHeadset_isConnectedA2dpDevice_returnFalse() {
+ when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
+ when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+ assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
+ mAudioManager)).isEqualTo(false);
+ }
+
+ @Test
+ public void isAvailableMediaBluetoothDevice_isA2dp_isConnectedA2dpDevice_returnTrue() {
+ when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
+ when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+ assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
+ mAudioManager)).isEqualTo(true);
+ }
+
+ @Test
+ public void isAvailableMediaBluetoothDevice_isHeadset_isConnectedHfpDevice_returnTrue() {
+ when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
+ when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+ assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
+ mAudioManager)).isEqualTo(true);
+ }
+
+ @Test
+ public void isConnectedBluetoothDevice_isConnectedLeAudioDevice_returnFalse() {
+ when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
+ mAudioManager)).isEqualTo(false);
+ }
+
+ @Test
+ public void isConnectedBluetoothDevice_isHeadset_isConnectedA2dpDevice_returnTrue() {
+ when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
+ when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
+ mAudioManager)).isEqualTo(true);
+ }
+
+ @Test
+ public void isConnectedBluetoothDevice_isA2dp_isConnectedA2dpDevice_returnFalse() {
+ when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
+ when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
+ mAudioManager)).isEqualTo(false);
+ }
+
+ @Test
+ public void isConnectedBluetoothDevice_isHeadset_isConnectedHfpDevice_returnFalse() {
+ when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
+ when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
+ mAudioManager)).isEqualTo(false);
+ }
+
+ @Test
+ public void isConnectedBluetoothDevice_isNotConnected_returnFalse() {
+ when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
+ when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mBluetoothDevice.isConnected()).thenReturn(false);
+
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
+ mAudioManager)).isEqualTo(false);
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 4b61ff1..85efe69 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -529,6 +529,51 @@
}
@Test
+ public void getConnectionSummary_testMemberDevicesExist_returnMinBattery() {
+ // One device is active with battery level 70.
+ mBatteryLevel = 70;
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
+
+
+ // Add a member device with battery level 30.
+ int lowerBatteryLevel = 30;
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ doAnswer((invocation) -> lowerBatteryLevel).when(mSubCachedDevice).getBatteryLevel();
+
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 30% battery");
+ }
+
+ @Test
+ public void getConnectionSummary_testMemberDevicesBatteryUnknown_returnMinBattery() {
+ // One device is active with battery level 70.
+ mBatteryLevel = 70;
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
+
+ // Add a member device with battery level unknown.
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ doAnswer((invocation) -> BluetoothDevice.BATTERY_LEVEL_UNKNOWN).when(
+ mSubCachedDevice).getBatteryLevel();
+
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 70% battery");
+ }
+
+ @Test
+ public void getConnectionSummary_testAllDevicesBatteryUnknown_returnNoBattery() {
+ // One device is active with battery level unknown.
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
+
+ // Add a member device with battery level unknown.
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ doAnswer((invocation) -> BluetoothDevice.BATTERY_LEVEL_UNKNOWN).when(
+ mSubCachedDevice).getBatteryLevel();
+
+ assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active");
+ }
+
+ @Test
public void getConnectionSummary_testMultipleProfilesActiveDevice() {
// Test without battery level
// Set A2DP and HFP profiles to be connected and test connection state summary
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 94a3173..1c33544 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -242,6 +242,7 @@
Settings.Secure.HEARING_AID_MEDIA_ROUTING,
Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING,
Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
- Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED
+ Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED,
+ Settings.Secure.SEARCH_LONG_PRESS_HOME_ENABLED
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 8179998..301fd6f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -201,6 +201,7 @@
VALIDATORS.put(Secure.ASSIST_TOUCH_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_LONG_PRESS_HOME_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.SEARCH_LONG_PRESS_HOME_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.VR_DISPLAY_MODE, new DiscreteValueValidator(new String[] {"0", "1"}));
VALIDATORS.put(Secure.NOTIFICATION_BADGING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.NOTIFICATION_DISMISS_RTL, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 7186aba..3c8d4bc 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1933,6 +1933,9 @@
dumpSetting(s, p,
Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED,
SecureSettingsProto.Assist.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_LONG_PRESS_HOME_ENABLED,
+ SecureSettingsProto.Assist.SEARCH_LONG_PRESS_HOME_ENABLED);
p.end(assistToken);
final long assistHandlesToken = p.start(SecureSettingsProto.ASSIST_HANDLES);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index b6bec1f..000612b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -240,6 +240,9 @@
/* Log fakes */
"tests/src/com/android/systemui/log/core/FakeLogBuffer.kt",
+
+ /* QS fakes */
+ "tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt",
],
path: "tests/src",
}
@@ -354,6 +357,11 @@
"tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt",
"tests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt",
"tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt",
+
+ /* Quick Settings new pipeline converted tests */
+ "tests/src/com/android/systemui/qs/pipeline/data/**/*Test.kt",
+ "tests/src/com/android/systemui/qs/pipeline/domain/**/*Test.kt",
+ "tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt",
],
path: "tests/src",
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index f358417..ff723e3 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -36,6 +36,7 @@
"androidx.preference_preference",
"androidx.viewpager_viewpager",
"SettingsLibDisplayUtils",
+ "com_android_a11y_menu_flags_lib",
],
optimize: {
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp
new file mode 100644
index 0000000..6d63409
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+ name: "com_android_a11y_menu_flags",
+ package: "com.android.systemui.accessibility.accessibilitymenu",
+ srcs: [
+ "accessibility.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "com_android_a11y_menu_flags_lib",
+ aconfig_declarations: "com_android_a11y_menu_flags",
+}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
new file mode 100644
index 0000000..03cbc16
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.systemui.accessibility.accessibilitymenu"
+
+flag {
+ name: "a11y_menu_settings_back_button_fix_and_large_button_sizing"
+ namespace: "accessibility"
+ description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons."
+ bug: "298467628"
+}
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index c26cd12..bf51e23 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -27,6 +27,7 @@
import android.provider.Settings;
import android.view.View;
import android.widget.TextView;
+import android.window.OnBackInvokedCallback;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
@@ -34,12 +35,16 @@
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
+import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.R;
/**
* Settings activity for AccessibilityMenu.
*/
public class A11yMenuSettingsActivity extends FragmentActivity {
+ private OnBackInvokedCallback mCallback = () -> {
+ finish();
+ };
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -51,6 +56,10 @@
ActionBar actionBar = getActionBar();
actionBar.setDisplayShowCustomEnabled(true);
+
+ if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
actionBar.setCustomView(R.layout.preferences_action_bar);
((TextView) findViewById(R.id.action_bar_title)).setText(
getResources().getString(R.string.accessibility_menu_settings_name)
@@ -61,6 +70,16 @@
| ActionBar.DISPLAY_HOME_AS_UP);
}
+ @Override
+ public boolean onNavigateUp() {
+ if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
+ mCallback.onBackInvoked();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
/**
* Settings/preferences fragment for AccessibilityMenu.
*/
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
index c64ec6f..c4f372c 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
@@ -28,6 +28,7 @@
import android.widget.TextView;
import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
+import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.R;
import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
@@ -79,6 +80,11 @@
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.grid_item, parent, false);
+
+ if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
+ configureShortcutSize(convertView,
+ A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService));
+ }
}
A11yMenuShortcut shortcutItem = (A11yMenuShortcut) getItem(position);
@@ -126,16 +132,29 @@
});
}
- private void configureShortcutView(View convertView, A11yMenuShortcut shortcutItem) {
+ private void configureShortcutSize(View convertView, boolean isLargeButtonsEnabled) {
ImageButton shortcutIconButton = convertView.findViewById(R.id.shortcutIconBtn);
TextView shortcutLabel = convertView.findViewById(R.id.shortcutLabel);
-
- if (A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService)) {
+ if (isLargeButtonsEnabled) {
ViewGroup.LayoutParams params = shortcutIconButton.getLayoutParams();
params.width = (int) (params.width * LARGE_BUTTON_SCALE);
params.height = (int) (params.height * LARGE_BUTTON_SCALE);
shortcutLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, mLargeTextSize);
}
+ }
+
+ private void configureShortcutView(View convertView, A11yMenuShortcut shortcutItem) {
+ ImageButton shortcutIconButton = convertView.findViewById(R.id.shortcutIconBtn);
+ TextView shortcutLabel = convertView.findViewById(R.id.shortcutLabel);
+
+ if (!Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
+ if (A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService)) {
+ ViewGroup.LayoutParams params = shortcutIconButton.getLayoutParams();
+ params.width = (int) (params.width * LARGE_BUTTON_SCALE);
+ params.height = (int) (params.height * LARGE_BUTTON_SCALE);
+ shortcutLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, mLargeTextSize);
+ }
+ }
if (shortcutItem.getId() == A11yMenuShortcut.ShortcutId.UNSPECIFIED_ID_VALUE.ordinal()) {
// Sets empty shortcut icon and label when the shortcut is ADD_ITEM.
diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml
index e474938..b77f78d 100644
--- a/packages/SystemUI/res/layout/media_projection_app_selector.xml
+++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml
@@ -20,10 +20,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
- androidprv:maxCollapsedHeight="0dp"
+ androidprv:maxCollapsedHeight="10000dp"
androidprv:maxCollapsedHeightSmall="56dp"
androidprv:maxWidth="@*android:dimen/chooser_width"
android:id="@*android:id/contentPanel">
+ <!-- maxCollapsedHeight above is huge, to make sure the layout is always expanded. -->
<LinearLayout
android:id="@*android:id/chooser_header"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 62c4424..b086ed8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1232,6 +1232,7 @@
<dimen name="magnification_setting_image_button_open_in_full_padding_horizontal">28dp</dimen>
<dimen name="magnification_setting_drag_corner_radius">28dp</dimen>
<dimen name="magnification_setting_drag_size">56dp</dimen>
+ <fraction name="magnification_resize_window_size_amount">10%</fraction>
<!-- Seekbar with icon buttons -->
<dimen name="seekbar_icon_size">24dp</dimen>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 0d45422..0903463 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -38,9 +38,6 @@
protected. -->
<bool name="flag_battery_shield_icon">false</bool>
- <!-- Whether face auth will immediately stop when the display state is OFF -->
- <bool name="flag_stop_face_auth_on_display_off">true</bool>
-
<!-- Whether we want to stop pulsing while running the face scanning animation -->
<bool name="flag_stop_pulsing_face_scanning_animation">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index e48901e..bd251bd 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -172,6 +172,10 @@
<item type="id" name="accessibility_action_move_right"/>
<item type="id" name="accessibility_action_move_up"/>
<item type="id" name="accessibility_action_move_down"/>
+ <item type="id" name="accessibility_action_increase_window_width"/>
+ <item type="id" name="accessibility_action_decrease_window_width"/>
+ <item type="id" name="accessibility_action_increase_window_height"/>
+ <item type="id" name="accessibility_action_decrease_window_height"/>
<!-- Accessibility actions for Accessibility floating menu. -->
<item type="id" name="action_move_top_left"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6840108..29c9767 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2403,6 +2403,16 @@
<string name="accessibility_control_move_left">Move left</string>
<!-- Action in accessibility menu to move the magnification window right. [CHAR LIMIT=30] -->
<string name="accessibility_control_move_right">Move right</string>
+
+ <!-- Action in accessibility menu to increase the magnification window width. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_control_increase_window_width">Increase width of magnifier</string>
+ <!-- Action in accessibility menu to decrease the magnification window width. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_control_decrease_window_width">Decrease width of magnifier</string>
+ <!-- Action in accessibility menu to increase the magnification window height. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_control_increase_window_height">Increase height of magnifier</string>
+ <!-- Action in accessibility menu to decrease the magnification window height. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_control_decrease_window_height">Decrease height of magnifier</string>
+
<!-- Content description for magnification mode switch. [CHAR LIMIT=NONE] -->
<string name="magnification_mode_switch_description">Magnification switch</string>
<!-- A11y state description for magnification mode switch that device is in full-screen mode. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index a2d8c50..7daea3f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -17,7 +17,6 @@
package com.android.keyguard;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
-
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
@@ -74,6 +73,7 @@
import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.biometrics.SideFpsUiRequestSource;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
@@ -94,6 +94,8 @@
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
+import dagger.Lazy;
+
import java.io.File;
import java.util.Optional;
@@ -201,7 +203,6 @@
};
private KeyguardSecurityCallback mKeyguardSecurityCallback = new KeyguardSecurityCallback() {
-
@Override
public void onUserInput() {
mBouncerMessageInteractor.onPrimaryBouncerUserInput();
@@ -297,21 +298,23 @@
*/
@Override
public void finish(int targetUserId) {
- // If there's a pending runnable because the user interacted with a widget
- // and we're leaving keyguard, then run it.
- boolean deferKeyguardDone = false;
- mWillRunDismissFromKeyguard = false;
- if (mDismissAction != null) {
- deferKeyguardDone = mDismissAction.onDismiss();
- mWillRunDismissFromKeyguard = mDismissAction.willRunAnimationOnKeyguard();
- mDismissAction = null;
- mCancelAction = null;
- }
- if (mViewMediatorCallback != null) {
- if (deferKeyguardDone) {
- mViewMediatorCallback.keyguardDonePending(targetUserId);
- } else {
- mViewMediatorCallback.keyguardDone(targetUserId);
+ if (!mFeatureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ // If there's a pending runnable because the user interacted with a widget
+ // and we're leaving keyguard, then run it.
+ boolean deferKeyguardDone = false;
+ mWillRunDismissFromKeyguard = false;
+ if (mDismissAction != null) {
+ deferKeyguardDone = mDismissAction.onDismiss();
+ mWillRunDismissFromKeyguard = mDismissAction.willRunAnimationOnKeyguard();
+ mDismissAction = null;
+ mCancelAction = null;
+ }
+ if (mViewMediatorCallback != null) {
+ if (deferKeyguardDone) {
+ mViewMediatorCallback.keyguardDonePending(targetUserId);
+ } else {
+ mViewMediatorCallback.keyguardDone(targetUserId);
+ }
}
}
@@ -326,7 +329,6 @@
}
};
-
private final SwipeListener mSwipeListener = new SwipeListener() {
@Override
public void onSwipeUp() {
@@ -416,6 +418,7 @@
private final Provider<AuthenticationInteractor> mAuthenticationInteractor;
private final Provider<JavaAdapter> mJavaAdapter;
private final DeviceProvisionedController mDeviceProvisionedController;
+ private final Lazy<PrimaryBouncerInteractor> mPrimaryBouncerInteractor;
@Nullable private Job mSceneTransitionCollectionJob;
@Inject
@@ -448,6 +451,7 @@
DeviceProvisionedController deviceProvisionedController,
FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
KeyguardTransitionInteractor keyguardTransitionInteractor,
+ Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
Provider<AuthenticationInteractor> authenticationInteractor
) {
super(view);
@@ -482,6 +486,7 @@
mJavaAdapter = javaAdapter;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mDeviceProvisionedController = deviceProvisionedController;
+ mPrimaryBouncerInteractor = primaryBouncerInteractor;
}
@Override
@@ -618,6 +623,9 @@
* @param action callback to be invoked when keyguard disappear animation completes.
*/
public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) {
+ if (mFeatureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ return;
+ }
if (mCancelAction != null) {
mCancelAction.run();
}
@@ -820,7 +828,6 @@
*/
public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId,
boolean bypassSecondaryLockScreen, SecurityMode expectedSecurityMode) {
-
if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")");
if (expectedSecurityMode != SecurityMode.Invalid
&& expectedSecurityMode != getCurrentSecurityMode()) {
@@ -829,8 +836,8 @@
return false;
}
+ boolean authenticatedWithPrimaryAuth = false;
boolean finish = false;
- boolean primaryAuth = false;
int eventSubtype = -1;
BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN;
if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
@@ -855,7 +862,7 @@
case Pattern:
case Password:
case PIN:
- primaryAuth = true;
+ authenticatedWithPrimaryAuth = true;
finish = true;
eventSubtype = BOUNCER_DISMISS_PASSWORD;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD;
@@ -901,6 +908,17 @@
if (uiEvent != BouncerUiEvent.UNKNOWN) {
mUiEventLogger.log(uiEvent, getSessionId());
}
+
+ if (mFeatureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (authenticatedWithPrimaryAuth) {
+ mPrimaryBouncerInteractor.get()
+ .notifyKeyguardAuthenticatedPrimaryAuth(targetUserId);
+ } else if (finish) {
+ mPrimaryBouncerInteractor.get().notifyUserRequestedBouncerWhenAlreadyAuthenticated(
+ targetUserId);
+ }
+ }
+
if (finish) {
mKeyguardSecurityCallback.finish(targetUserId);
}
@@ -1058,12 +1076,15 @@
* one side).
*/
private boolean canUseOneHandedBouncer() {
- if (!(mCurrentSecurityMode == SecurityMode.Pattern
- || mCurrentSecurityMode == SecurityMode.PIN)) {
- return false;
+ switch(mCurrentSecurityMode) {
+ case PIN:
+ case Pattern:
+ case SimPin:
+ case SimPuk:
+ return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
+ default:
+ return false;
}
-
- return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
}
private boolean canDisplayUserSwitcher() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 886a1b5..1fc88ab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -141,10 +141,10 @@
// GSM 02.17 version 5.0.1, Section 5.6 PIN Management
if ((entry.length() < 4) || (entry.length() > 8)) {
// otherwise, display a message to the user, and don't submit.
- mMessageAreaController.setMessage(
- com.android.systemui.R.string.kg_invalid_sim_pin_hint);
mView.resetPasswordText(true /* animate */, true /* announce */);
getKeyguardSecurityCallback().userActivity();
+ mMessageAreaController.setMessage(
+ com.android.systemui.R.string.kg_invalid_sim_pin_hint);
return;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 0ba6c05..e4bbd3a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -35,7 +35,6 @@
import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT;
import static android.os.PowerManager.WAKE_REASON_UNKNOWN;
-
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -161,8 +160,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.DumpsysTableLogger;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent;
@@ -417,7 +414,6 @@
private final ActiveUnlockConfig mActiveUnlockConfig;
private final IDreamManager mDreamManager;
private final TelephonyManager mTelephonyManager;
- private final FeatureFlags mFeatureFlags;
@Nullable
private final FingerprintManager mFpm;
@Nullable
@@ -2365,7 +2361,6 @@
FaceWakeUpTriggersConfig faceWakeUpTriggersConfig,
DevicePostureController devicePostureController,
Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider,
- FeatureFlags featureFlags,
TaskStackChangeListeners taskStackChangeListeners,
IActivityTaskManager activityTaskManagerService,
DisplayTracker displayTracker) {
@@ -2400,7 +2395,6 @@
mPackageManager = packageManager;
mFpm = fingerprintManager;
mFaceManager = faceManager;
- mFeatureFlags = featureFlags;
mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
mFaceAcquiredInfoIgnoreList = Arrays.stream(
mContext.getResources().getIntArray(
@@ -2417,9 +2411,7 @@
mTaskStackChangeListeners = taskStackChangeListeners;
mActivityTaskManager = activityTaskManagerService;
mDisplayTracker = displayTracker;
- if (mFeatureFlags.isEnabled(Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF)) {
- mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
- }
+ mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
mHandler = new Handler(mainLooper) {
@Override
@@ -4205,21 +4197,19 @@
@Override
public void onTaskStackChangedBackground() {
try {
- if (mFeatureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
- RootTaskInfo standardTask = mActivityTaskManager.getRootTaskInfo(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final boolean previousState = mAllowFingerprintOnCurrentOccludingActivity;
- mAllowFingerprintOnCurrentOccludingActivity =
- standardTask.topActivity != null
- && !TextUtils.isEmpty(standardTask.topActivity.getPackageName())
- && mAllowFingerprintOnOccludingActivitiesFromPackage.contains(
- standardTask.topActivity.getPackageName())
- && standardTask.visible;
- if (mAllowFingerprintOnCurrentOccludingActivity != previousState) {
- mLogger.allowFingerprintOnCurrentOccludingActivityChanged(
- mAllowFingerprintOnCurrentOccludingActivity);
- updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
- }
+ RootTaskInfo standardTask = mActivityTaskManager.getRootTaskInfo(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ final boolean previousState = mAllowFingerprintOnCurrentOccludingActivity;
+ mAllowFingerprintOnCurrentOccludingActivity =
+ standardTask.topActivity != null
+ && !TextUtils.isEmpty(standardTask.topActivity.getPackageName())
+ && mAllowFingerprintOnOccludingActivitiesFromPackage.contains(
+ standardTask.topActivity.getPackageName())
+ && standardTask.visible;
+ if (mAllowFingerprintOnCurrentOccludingActivity != previousState) {
+ mLogger.allowFingerprintOnCurrentOccludingActivityChanged(
+ mAllowFingerprintOnCurrentOccludingActivity);
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
RootTaskInfo assistantTask = mActivityTaskManager.getRootTaskInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 03ad132..7a8161e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -17,7 +17,6 @@
package com.android.systemui.accessibility;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
-
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import android.accessibilityservice.AccessibilityService;
@@ -57,8 +56,8 @@
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.Assert;
import dagger.Lazy;
@@ -186,8 +185,8 @@
private final DisplayTracker mDisplayTracker;
private Locale mLocale;
private final AccessibilityManager mA11yManager;
- private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
private final NotificationShadeWindowController mNotificationShadeController;
+ private final KeyguardStateController mKeyguardStateController;
private final ShadeController mShadeController;
private final Lazy<ShadeViewController> mShadeViewController;
private final StatusBarWindowCallback mNotificationShadeCallback;
@@ -197,13 +196,14 @@
public SystemActions(Context context,
UserTracker userTracker,
NotificationShadeWindowController notificationShadeController,
+ KeyguardStateController keyguardStateController,
ShadeController shadeController,
Lazy<ShadeViewController> shadeViewController,
- Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Optional<Recents> recentsOptional,
DisplayTracker displayTracker) {
mContext = context;
mUserTracker = userTracker;
+ mKeyguardStateController = keyguardStateController;
mShadeController = shadeController;
mShadeViewController = shadeViewController;
mRecentsOptional = recentsOptional;
@@ -219,7 +219,6 @@
(keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, mDozing,
panelExpanded, isDreaming) ->
registerOrUnregisterDismissNotificationShadeAction();
- mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
}
@Override
@@ -307,8 +306,8 @@
mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
- if (mCentralSurfacesOptionalLazy.get().isPresent()) {
- // These two actions require the CentralSurfaces instance.
+ if (mShadeController.isShadeEnabled()) {
+ // These two actions require the shade to be enabled.
mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
}
@@ -329,13 +328,8 @@
private void registerOrUnregisterDismissNotificationShadeAction() {
Assert.isMainThread();
- // Saving state in instance variable since this callback is called quite often to avoid
- // binder calls
- final Optional<CentralSurfaces> centralSurfacesOptional =
- mCentralSurfacesOptionalLazy.get();
- if (centralSurfacesOptional.isPresent()
- && mShadeViewController.get().isPanelExpanded()
- && !centralSurfacesOptional.get().isKeyguardShowing()) {
+ if (mShadeViewController.get().isPanelExpanded()
+ && !mKeyguardStateController.isShowing()) {
if (!mDismissNotificationShadeActionRegistered) {
mA11yManager.registerSystemAction(
createRemoteAction(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index f1cebba..773241e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -26,6 +26,7 @@
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiContext;
@@ -719,6 +720,18 @@
}
/**
+ * Sets the window frame size with given width and height in pixels without changing the
+ * window center.
+ *
+ * @param width the window frame width in pixels
+ * @param height the window frame height in pixels.
+ */
+ @MainThread
+ private void setMagnificationFrameSize(int width, int height) {
+ setWindowSize(width + 2 * mMirrorSurfaceMargin, height + 2 * mMirrorSurfaceMargin);
+ }
+
+ /**
* Sets the window size with given width and height in pixels without changing the
* window center. The width or the height will be clamped in the range
* [{@link #mMinWindowSize}, screen width or height].
@@ -1496,19 +1509,50 @@
AccessibilityAction.ACTION_CLICK.getId(), getClickAccessibilityActionLabel());
info.addAction(clickAction);
info.setClickable(true);
+
info.addAction(
new AccessibilityAction(R.id.accessibility_action_zoom_in,
mContext.getString(R.string.accessibility_control_zoom_in)));
info.addAction(new AccessibilityAction(R.id.accessibility_action_zoom_out,
mContext.getString(R.string.accessibility_control_zoom_out)));
- info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up,
- mContext.getString(R.string.accessibility_control_move_up)));
- info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down,
- mContext.getString(R.string.accessibility_control_move_down)));
- info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left,
- mContext.getString(R.string.accessibility_control_move_left)));
- info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right,
- mContext.getString(R.string.accessibility_control_move_right)));
+
+ if (!mEditSizeEnable) {
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up,
+ mContext.getString(R.string.accessibility_control_move_up)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down,
+ mContext.getString(R.string.accessibility_control_move_down)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left,
+ mContext.getString(R.string.accessibility_control_move_left)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right,
+ mContext.getString(R.string.accessibility_control_move_right)));
+ } else {
+ if ((mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin)
+ < mWindowBounds.width()) {
+ info.addAction(new AccessibilityAction(
+ R.id.accessibility_action_increase_window_width,
+ mContext.getString(
+ R.string.accessibility_control_increase_window_width)));
+ }
+ if ((mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin)
+ < mWindowBounds.height()) {
+ info.addAction(new AccessibilityAction(
+ R.id.accessibility_action_increase_window_height,
+ mContext.getString(
+ R.string.accessibility_control_increase_window_height)));
+ }
+ if ((mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin) > mMinWindowSize) {
+ info.addAction(new AccessibilityAction(
+ R.id.accessibility_action_decrease_window_width,
+ mContext.getString(
+ R.string.accessibility_control_decrease_window_width)));
+ }
+ if ((mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin) > mMinWindowSize) {
+ info.addAction(new AccessibilityAction(
+ R.id.accessibility_action_decrease_window_height,
+ mContext.getString(
+ R.string.accessibility_control_decrease_window_height)));
+ }
+ }
info.setContentDescription(mContext.getString(R.string.magnification_window_title));
info.setStateDescription(formatStateDescription(getScale()));
@@ -1523,6 +1567,11 @@
}
private boolean performA11yAction(int action) {
+ final float changeWindowSizeAmount = mContext.getResources().getFraction(
+ R.fraction.magnification_resize_window_size_amount,
+ /* base= */ 1,
+ /* pbase= */ 1);
+
if (action == AccessibilityAction.ACTION_CLICK.getId()) {
if (mEditSizeEnable) {
// When edit mode is enabled, click the magnifier to exit edit mode.
@@ -1544,9 +1593,26 @@
move(-mSourceBounds.width(), 0);
} else if (action == R.id.accessibility_action_move_right) {
move(mSourceBounds.width(), 0);
+ } else if (action == R.id.accessibility_action_increase_window_width) {
+ int newFrameWidth =
+ (int) (mMagnificationFrame.width() * (1 + changeWindowSizeAmount));
+ setMagnificationFrameSize(newFrameWidth, mMagnificationFrame.height());
+ } else if (action == R.id.accessibility_action_increase_window_height) {
+ int newFrameHeight =
+ (int) (mMagnificationFrame.height() * (1 + changeWindowSizeAmount));
+ setMagnificationFrameSize(mMagnificationFrame.width(), newFrameHeight);
+ } else if (action == R.id.accessibility_action_decrease_window_width) {
+ int newFrameWidth =
+ (int) (mMagnificationFrame.width() * (1 - changeWindowSizeAmount));
+ setMagnificationFrameSize(newFrameWidth, mMagnificationFrame.height());
+ } else if (action == R.id.accessibility_action_decrease_window_height) {
+ int newFrameHeight =
+ (int) (mMagnificationFrame.height() * (1 - changeWindowSizeAmount));
+ setMagnificationFrameSize(mMagnificationFrame.width(), newFrameHeight);
} else {
return false;
}
+
mWindowMagnifierCallback.onAccessibilityActionPerformed(mDisplayId);
return true;
}
@@ -1557,4 +1623,5 @@
mDisplayId, scale, /* updatePersistence= */ true);
}
}
+
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 94b5fb2..b8c0e2c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -18,14 +18,21 @@
import android.animation.ValueAnimator
import android.graphics.PointF
import android.graphics.RectF
-import com.android.systemui.Dumpable
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
+import com.android.systemui.Dumpable
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionListener
-import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.util.ViewController
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
import java.io.PrintWriter
/**
@@ -41,7 +48,7 @@
abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
view: T,
protected val statusBarStateController: StatusBarStateController,
- protected val shadeExpansionStateManager: ShadeExpansionStateManager,
+ protected val primaryBouncerInteractor: PrimaryBouncerInteractor,
protected val dialogManager: SystemUIDialogManager,
private val dumpManager: DumpManager
) : ViewController<T>(view), Dumpable {
@@ -54,14 +61,6 @@
private var dialogAlphaAnimator: ValueAnimator? = null
private val dialogListener = SystemUIDialogManager.Listener { runDialogAlphaAnimator() }
- private val shadeExpansionListener = ShadeExpansionListener { event ->
- // Notification shade can be expanded but not visible (fraction: 0.0), for example
- // when a heads-up notification (HUN) is showing.
- notificationShadeVisible = event.expanded && event.fraction > 0f
- view.onExpansionChanged(event.fraction)
- updatePauseAuth()
- }
-
/** If the notification shade is visible. */
var notificationShadeVisible: Boolean = false
@@ -88,6 +87,28 @@
view.updateAlpha()
}
+ init {
+ view.repeatWhenAttached {
+ // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
+ // can make the view not visible; and we still want to listen for events
+ // that may make the view visible again.
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ listenForBouncerExpansion(this)
+ }
+ }
+ }
+
+ @VisibleForTesting
+ open suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+ return scope.launch {
+ primaryBouncerInteractor.bouncerExpansion.map { 1f - it }.collect { expansion: Float ->
+ notificationShadeVisible = expansion > 0f
+ view.onExpansionChanged(expansion)
+ updatePauseAuth()
+ }
+ }
+ }
+
fun runDialogAlphaAnimator() {
val hideAffordance = dialogManager.shouldHideAffordance()
dialogAlphaAnimator?.cancel()
@@ -108,15 +129,11 @@
}
override fun onViewAttached() {
- val currentState =
- shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
- shadeExpansionListener.onPanelExpansionChanged(currentState)
dialogManager.registerListener(dialogListener)
dumpManager.registerDumpable(dumpTag, this)
}
override fun onViewDetached() {
- shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
dialogManager.unregisterListener(dialogListener)
dumpManager.unregisterDumpable(dumpTag)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index 802eea3..03749a9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -15,9 +15,9 @@
*/
package com.android.systemui.biometrics
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
/**
@@ -26,13 +26,13 @@
class UdfpsBpViewController(
view: UdfpsBpView,
statusBarStateController: StatusBarStateController,
- shadeExpansionStateManager: ShadeExpansionStateManager,
+ primaryBouncerInteractor: PrimaryBouncerInteractor,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsBpView>(
view,
statusBarStateController,
- shadeExpansionStateManager,
+ primaryBouncerInteractor,
systemUIDialogManager,
dumpManager
) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index a368703..11a578b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -23,7 +23,6 @@
import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING;
import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR;
-
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
@@ -60,6 +59,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -92,7 +92,6 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -118,6 +117,7 @@
import javax.inject.Inject;
import javax.inject.Provider;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
/**
* Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events,
* and toggles the UDFPS display mode.
@@ -149,7 +149,6 @@
private final WindowManager mWindowManager;
private final DelayableExecutor mFgExecutor;
@NonNull private final Executor mBiometricExecutor;
- @NonNull private final ShadeExpansionStateManager mShadeExpansionStateManager;
@NonNull private final StatusBarStateController mStatusBarStateController;
@NonNull private final KeyguardStateController mKeyguardStateController;
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@@ -264,23 +263,44 @@
}
public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
+ @OptIn(markerClass = ExperimentalCoroutinesApi.class)
@Override
public void showUdfpsOverlay(long requestId, int sensorId, int reason,
@NonNull IUdfpsOverlayControllerCallback callback) {
mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay(
- new UdfpsControllerOverlay(mContext, mFingerprintManager, mInflater,
- mWindowManager, mAccessibilityManager, mStatusBarStateController,
- mShadeExpansionStateManager, mKeyguardViewManager,
- mKeyguardUpdateMonitor, mDialogManager, mDumpManager,
- mLockscreenShadeTransitionController, mConfigurationController,
- mKeyguardStateController,
- mUnlockedScreenOffAnimationController,
- mUdfpsDisplayMode, mSecureSettings, requestId, reason, callback,
- (view, event, fromUdfpsView) -> onTouch(requestId, event,
- fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
- mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils,
- mUdfpsKeyguardAccessibilityDelegate,
- mUdfpsKeyguardViewModels)));
+ new UdfpsControllerOverlay(
+ mContext,
+ mFingerprintManager,
+ mInflater,
+ mWindowManager,
+ mAccessibilityManager,
+ mStatusBarStateController,
+ mKeyguardViewManager,
+ mKeyguardUpdateMonitor,
+ mDialogManager,
+ mDumpManager,
+ mLockscreenShadeTransitionController,
+ mConfigurationController,
+ mKeyguardStateController,
+ mUnlockedScreenOffAnimationController,
+ mUdfpsDisplayMode,
+ mSecureSettings,
+ requestId,
+ reason,
+ callback,
+ (view, event, fromUdfpsView) -> onTouch(
+ requestId,
+ event,
+ fromUdfpsView
+ ),
+ mActivityLaunchAnimator,
+ mFeatureFlags,
+ mPrimaryBouncerInteractor,
+ mAlternateBouncerInteractor,
+ mUdfpsUtils,
+ mUdfpsKeyguardAccessibilityDelegate,
+ mUdfpsKeyguardViewModels
+ )));
}
@Override
@@ -814,7 +834,6 @@
@NonNull WindowManager windowManager,
@NonNull StatusBarStateController statusBarStateController,
@Main DelayableExecutor fgExecutor,
- @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull DumpManager dumpManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -859,7 +878,6 @@
mFingerprintManager = checkNotNull(fingerprintManager);
mWindowManager = windowManager;
mFgExecutor = fgExecutor;
- mShadeExpansionStateManager = shadeExpansionStateManager;
mStatusBarStateController = statusBarStateController;
mKeyguardStateController = keyguardStateController;
mKeyguardViewManager = statusBarKeyguardViewManager;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index d6ef94d..4031f2f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -60,7 +60,6 @@
import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -90,7 +89,6 @@
private val windowManager: WindowManager,
private val accessibilityManager: AccessibilityManager,
private val statusBarStateController: StatusBarStateController,
- private val shadeExpansionStateManager: ShadeExpansionStateManager,
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dialogManager: SystemUIDialogManager,
@@ -245,7 +243,7 @@
updateAccessibilityViewLocation(sensorBounds)
},
statusBarStateController,
- shadeExpansionStateManager,
+ primaryBouncerInteractor,
dialogManager,
dumpManager
)
@@ -256,7 +254,7 @@
UdfpsKeyguardViewController(
view.addUdfpsView(R.layout.udfps_keyguard_view),
statusBarStateController,
- shadeExpansionStateManager,
+ primaryBouncerInteractor,
dialogManager,
dumpManager,
alternateBouncerInteractor,
@@ -268,7 +266,6 @@
updateSensorLocation(sensorBounds)
},
statusBarStateController,
- shadeExpansionStateManager,
statusBarKeyguardViewManager,
keyguardUpdateMonitor,
dumpManager,
@@ -291,7 +288,7 @@
UdfpsBpViewController(
view.addUdfpsView(R.layout.udfps_bp_view),
statusBarStateController,
- shadeExpansionStateManager,
+ primaryBouncerInteractor,
dialogManager,
dumpManager
)
@@ -301,7 +298,7 @@
UdfpsFpmEmptyViewController(
view.addUdfpsView(R.layout.udfps_fpm_empty_view),
statusBarStateController,
- shadeExpansionStateManager,
+ primaryBouncerInteractor,
dialogManager,
dumpManager
)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
index d122d64..88002e7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
@@ -15,9 +15,9 @@
*/
package com.android.systemui.biometrics
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
/**
@@ -28,13 +28,13 @@
class UdfpsFpmEmptyViewController(
view: UdfpsFpmEmptyView,
statusBarStateController: StatusBarStateController,
- shadeExpansionStateManager: ShadeExpansionStateManager,
+ primaryBouncerInteractor: PrimaryBouncerInteractor,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsFpmEmptyView>(
view,
statusBarStateController,
- shadeExpansionStateManager,
+ primaryBouncerInteractor,
systemUIDialogManager,
dumpManager
) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index e3fd3ce1..84a746c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -37,8 +37,6 @@
import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionListener
-import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
@@ -55,11 +53,9 @@
import kotlinx.coroutines.launch
/** Class that coordinates non-HBM animations during keyguard authentication. */
-open class UdfpsKeyguardViewControllerLegacy
-constructor(
+open class UdfpsKeyguardViewControllerLegacy(
private val view: UdfpsKeyguardViewLegacy,
statusBarStateController: StatusBarStateController,
- shadeExpansionStateManager: ShadeExpansionStateManager,
private val keyguardViewManager: StatusBarKeyguardViewManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
dumpManager: DumpManager,
@@ -71,14 +67,14 @@
private val udfpsController: UdfpsController,
private val activityLaunchAnimator: ActivityLaunchAnimator,
featureFlags: FeatureFlags,
- private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
) :
UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
view,
statusBarStateController,
- shadeExpansionStateManager,
+ primaryBouncerInteractor,
systemUIDialogManager,
dumpManager,
),
@@ -159,17 +155,6 @@
}
}
- private val shadeExpansionListener = ShadeExpansionListener { (fraction) ->
- panelExpansionFraction =
- if (keyguardViewManager.isPrimaryBouncerInTransit) {
- aboutToShowBouncerProgress(fraction)
- } else {
- fraction
- }
- updateAlpha()
- updatePauseAuth()
- }
-
private val keyguardStateControllerCallback: KeyguardStateController.Callback =
object : KeyguardStateController.Callback {
override fun onUnlockedChanged() {
@@ -262,10 +247,17 @@
}
@VisibleForTesting
- suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+ override suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
return scope.launch {
primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
inputBouncerExpansion = bouncerExpansion
+
+ panelExpansionFraction =
+ if (keyguardViewManager.isPrimaryBouncerInTransit) {
+ aboutToShowBouncerProgress(1f - bouncerExpansion)
+ } else {
+ 1f - bouncerExpansion
+ }
updateAlpha()
updatePauseAuth()
}
@@ -295,8 +287,6 @@
qsExpansion = keyguardViewManager.qsExpansion
keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback)
configurationController.addCallback(configurationListener)
- val currentState = shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
- shadeExpansionListener.onPanelExpansionChanged(currentState)
updateScaleFactor()
view.updatePadding()
updateAlpha()
@@ -321,7 +311,6 @@
keyguardViewManager.removeOccludingAppBiometricUI(occludingAppBiometricUI)
keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
configurationController.removeCallback(configurationListener)
- shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
if (lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy === this) {
lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index daff5fe..aa33100 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -16,13 +16,19 @@
package com.android.systemui.biometrics.data.repository
+import android.Manifest.permission.USE_BIOMETRIC_INTERNAL
+import android.annotation.RequiresPermission
+import android.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.biometrics.shared.model.toSensorStrength
+import com.android.systemui.biometrics.shared.model.toSensorType
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -31,11 +37,10 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/**
* A repository for the global state of FingerprintProperty.
@@ -44,22 +49,17 @@
*/
interface FingerprintPropertyRepository {
- /**
- * If the repository is initialized or not. Other properties are defaults until this is true.
- */
- val isInitialized: Flow<Boolean>
-
/** The id of fingerprint sensor. */
- val sensorId: StateFlow<Int>
+ val sensorId: Flow<Int>
/** The security strength of sensor (convenience, weak, strong). */
- val strength: StateFlow<SensorStrength>
+ val strength: Flow<SensorStrength>
/** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */
- val sensorType: StateFlow<FingerprintSensorType>
+ val sensorType: Flow<FingerprintSensorType>
/** The sensor location relative to each physical display. */
- val sensorLocations: StateFlow<Map<String, SensorLocationInternal>>
+ val sensorLocations: Flow<Map<String, SensorLocationInternal>>
}
@SysUISingleton
@@ -70,64 +70,64 @@
private val fingerprintManager: FingerprintManager?,
) : FingerprintPropertyRepository {
- override val isInitialized: Flow<Boolean> =
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ private val props: StateFlow<FingerprintSensorPropertiesInternal> =
conflatedCallbackFlow {
val callback =
object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
override fun onAllAuthenticatorsRegistered(
sensors: List<FingerprintSensorPropertiesInternal>
) {
- if (sensors.isNotEmpty()) {
- setProperties(sensors[0])
- trySendWithFailureLogging(true, TAG, "initialize properties")
+ if (sensors.isEmpty()) {
+ trySendWithFailureLogging(
+ DEFAULT_PROPS,
+ TAG,
+ "no registered sensors, use default props"
+ )
+ } else {
+ trySendWithFailureLogging(
+ sensors[0],
+ TAG,
+ "update properties on authenticators registered"
+ )
}
}
}
fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
- trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
awaitClose {}
}
- .shareIn(scope = applicationScope, started = SharingStarted.Eagerly, replay = 1)
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = DEFAULT_PROPS,
+ )
- private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
- override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
+ override val sensorId: Flow<Int> = props.map { it.sensorId }
- private val _strength: MutableStateFlow<SensorStrength> =
- MutableStateFlow(SensorStrength.CONVENIENCE)
- override val strength = _strength.asStateFlow()
+ override val strength: Flow<SensorStrength> = props.map { it.sensorStrength.toSensorStrength() }
- private val _sensorType: MutableStateFlow<FingerprintSensorType> =
- MutableStateFlow(FingerprintSensorType.UNKNOWN)
- override val sensorType = _sensorType.asStateFlow()
+ override val sensorType: Flow<FingerprintSensorType> =
+ props.map { it.sensorType.toSensorType() }
- private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
- MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
- override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
- _sensorLocations.asStateFlow()
-
- private fun setProperties(prop: FingerprintSensorPropertiesInternal) {
- _sensorId.value = prop.sensorId
- _strength.value = prop.sensorStrength.toSensorStrength()
- _sensorType.value = sensorTypeIntToObject(prop.sensorType)
- _sensorLocations.value =
- prop.allLocations.associateBy { sensorLocationInternal ->
+ override val sensorLocations: Flow<Map<String, SensorLocationInternal>> =
+ props.map {
+ it.allLocations.associateBy { sensorLocationInternal ->
sensorLocationInternal.displayId
}
- }
+ }
companion object {
private const val TAG = "FingerprintPropertyRepositoryImpl"
- }
-}
-
-private fun sensorTypeIntToObject(value: Int): FingerprintSensorType {
- return when (value) {
- 0 -> FingerprintSensorType.UNKNOWN
- 1 -> FingerprintSensorType.REAR
- 2 -> FingerprintSensorType.UDFPS_ULTRASONIC
- 3 -> FingerprintSensorType.UDFPS_OPTICAL
- 4 -> FingerprintSensorType.POWER_BUTTON
- 5 -> FingerprintSensorType.HOME_BUTTON
- else -> throw IllegalArgumentException("Invalid SensorType value: $value")
+ private val DEFAULT_PROPS =
+ FingerprintSensorPropertiesInternal(
+ -1 /* sensorId */,
+ SensorProperties.STRENGTH_CONVENIENCE,
+ 0 /* maxEnrollmentsPerUser */,
+ listOf<ComponentInfoInternal>(),
+ FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* halControlsIllumination */,
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index 26b6f2a..2e734a3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -18,13 +18,16 @@
import android.content.Context
import android.content.res.Configuration
+import android.view.Display
import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.util.kotlin.sample
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -32,10 +35,14 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Aggregates display state information. */
interface DisplayStateInteractor {
+ /** Whether the default display is currently off. */
+ val isDefaultDisplayOff: Flow<Boolean>
/** Whether the device is currently in rear display mode. */
val isInRearDisplayMode: StateFlow<Boolean>
@@ -55,6 +62,7 @@
@Application context: Context,
@Main mainExecutor: Executor,
rearDisplayStateRepository: RearDisplayStateRepository,
+ displayRepository: DisplayRepository,
) : DisplayStateInteractor {
private var screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
@@ -96,6 +104,17 @@
screenSizeFoldProvider.onConfigurationChange(newConfig)
}
+ private val defaultDisplay =
+ displayRepository.displays.map { displays ->
+ displays.firstOrNull { it.displayId == Display.DEFAULT_DISPLAY }
+ }
+
+ override val isDefaultDisplayOff =
+ displayRepository.displayChangeEvent
+ .filter { it == Display.DEFAULT_DISPLAY }
+ .sample(defaultDisplay)
+ .map { it?.state == Display.STATE_OFF }
+
companion object {
private const val TAG = "DisplayStateInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index 5badcaf..a6ad24e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -32,7 +32,6 @@
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -69,7 +68,7 @@
val isConfirmationRequired: Flow<Boolean>
/** Fingerprint sensor type */
- val sensorType: StateFlow<FingerprintSensorType>
+ val sensorType: Flow<FingerprintSensorType>
/** Use biometrics for authentication. */
fun useBiometricsForAuthentication(
@@ -95,7 +94,7 @@
class PromptSelectorInteractorImpl
@Inject
constructor(
- private val fingerprintPropertyRepository: FingerprintPropertyRepository,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
private val promptRepository: PromptRepository,
lockPatternUtils: LockPatternUtils,
) : PromptSelectorInteractor {
@@ -147,8 +146,7 @@
}
}
- override val sensorType: StateFlow<FingerprintSensorType> =
- fingerprintPropertyRepository.sensorType
+ override val sensorType: Flow<FingerprintSensorType> = fingerprintPropertyRepository.sensorType
override fun useBiometricsForAuthentication(
promptInfo: PromptInfo,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
index aa85e5f3..75ae061 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
@@ -17,32 +17,43 @@
package com.android.systemui.biometrics.domain.interactor
import android.hardware.biometrics.SensorLocationInternal
-import android.util.Log
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
/** Business logic for SideFps overlay offsets. */
interface SideFpsOverlayInteractor {
- /** Get the corresponding offsets based on different displayId. */
- fun getOverlayOffsets(displayId: String): SensorLocationInternal
+ /** The displayId of the current display. */
+ val displayId: Flow<String>
+
+ /** Overlay offsets corresponding to given displayId. */
+ val overlayOffsets: Flow<SensorLocationInternal>
+
+ /** Called on display changes, used to keep the display state in sync */
+ fun onDisplayChanged(displayId: String)
}
@SysUISingleton
class SideFpsOverlayInteractorImpl
@Inject
-constructor(private val fingerprintPropertyRepository: FingerprintPropertyRepository) :
+constructor(fingerprintPropertyRepository: FingerprintPropertyRepository) :
SideFpsOverlayInteractor {
- override fun getOverlayOffsets(displayId: String): SensorLocationInternal {
- val offsets = fingerprintPropertyRepository.sensorLocations.value
- return if (offsets.containsKey(displayId)) {
- offsets[displayId]!!
- } else {
- Log.w(TAG, "No location specified for display: $displayId")
- offsets[""]!!
+ private val _displayId: MutableStateFlow<String> = MutableStateFlow("")
+ override val displayId: Flow<String> = _displayId.asStateFlow()
+
+ override val overlayOffsets: Flow<SensorLocationInternal> =
+ combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets ->
+ offsets[displayId] ?: SensorLocationInternal.DEFAULT
}
+
+ override fun onDisplayChanged(displayId: String) {
+ _displayId.value = displayId
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
index df5cefd..c6fdcb3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
@@ -27,3 +27,15 @@
POWER_BUTTON,
HOME_BUTTON,
}
+
+/** Convert [this] to corresponding [FingerprintSensorType] */
+fun Int.toSensorType(): FingerprintSensorType =
+ when (this) {
+ FingerprintSensorProperties.TYPE_UNKNOWN -> FingerprintSensorType.UNKNOWN
+ FingerprintSensorProperties.TYPE_REAR -> FingerprintSensorType.REAR
+ FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC -> FingerprintSensorType.UDFPS_ULTRASONIC
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL -> FingerprintSensorType.UDFPS_OPTICAL
+ FingerprintSensorProperties.TYPE_POWER_BUTTON -> FingerprintSensorType.POWER_BUTTON
+ FingerprintSensorProperties.TYPE_HOME_BUTTON -> FingerprintSensorType.HOME_BUTTON
+ else -> throw IllegalArgumentException("Invalid SensorType value: $this")
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
index 30e865e..476daac 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
@@ -28,8 +28,8 @@
/** Convert [this] to corresponding [SensorStrength] */
fun Int.toSensorStrength(): SensorStrength =
when (this) {
- 0 -> SensorStrength.CONVENIENCE
- 1 -> SensorStrength.WEAK
- 2 -> SensorStrength.STRONG
+ SensorProperties.STRENGTH_CONVENIENCE -> SensorStrength.CONVENIENCE
+ SensorProperties.STRENGTH_WEAK -> SensorStrength.WEAK
+ SensorProperties.STRENGTH_STRONG -> SensorStrength.STRONG
else -> throw IllegalArgumentException("Invalid SensorStrength value: $this")
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
index c9b1624..6f4e1a3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
@@ -19,11 +19,11 @@
import com.android.systemui.biometrics.UdfpsAnimationViewController
import com.android.systemui.biometrics.UdfpsKeyguardView
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -32,7 +32,7 @@
open class UdfpsKeyguardViewController(
val view: UdfpsKeyguardView,
statusBarStateController: StatusBarStateController,
- shadeExpansionStateManager: ShadeExpansionStateManager,
+ primaryBouncerInteractor: PrimaryBouncerInteractor,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
@@ -41,7 +41,7 @@
UdfpsAnimationViewController<UdfpsKeyguardView>(
view,
statusBarStateController,
- shadeExpansionStateManager,
+ primaryBouncerInteractor,
systemUIDialogManager,
dumpManager,
),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
index 9b30acb..b406ea4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
@@ -33,7 +33,7 @@
@Inject
constructor(
private val displayStateInteractor: DisplayStateInteractor,
- private val promptSelectorInteractor: PromptSelectorInteractor,
+ promptSelectorInteractor: PromptSelectorInteractor,
) {
/** Current device rotation. */
private var rotation: Int = Surface.ROTATION_0
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
index f2b4e09..c0b2153 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
@@ -28,8 +28,11 @@
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
@@ -47,8 +50,10 @@
val primaryBouncerShowingSoon: StateFlow<Boolean>
val primaryBouncerStartingToHide: StateFlow<Boolean>
val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
+
/** Determines if we want to instantaneously show the primary bouncer instead of translating. */
val primaryBouncerScrimmed: StateFlow<Boolean>
+
/**
* Set how much of the notification panel is showing on the screen.
*
@@ -60,8 +65,23 @@
val panelExpansionAmount: StateFlow<Float>
val keyguardPosition: StateFlow<Float?>
val isBackButtonEnabled: StateFlow<Boolean?>
- /** Determines if user is already unlocked */
- val keyguardAuthenticated: StateFlow<Boolean?>
+
+ /**
+ * Triggers when the user has successfully used biometrics to authenticate. True = biometrics
+ * used to authenticate is Class 3, else false. When null, biometrics haven't authenticated the
+ * device.
+ */
+ val keyguardAuthenticatedBiometrics: StateFlow<Boolean?>
+
+ /**
+ * Triggers when the given userId (Int) has successfully used primary authentication to
+ * authenticate
+ */
+ val keyguardAuthenticatedPrimaryAuth: Flow<Int>
+
+ /** Triggers when the given userId (Int) has requested the bouncer when already authenticated */
+ val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int>
+
val showMessage: StateFlow<BouncerShowMessageModel?>
val resourceUpdateRequests: StateFlow<Boolean>
val alternateBouncerVisible: StateFlow<Boolean>
@@ -88,7 +108,11 @@
fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?)
- fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?)
+ fun setKeyguardAuthenticatedBiometrics(keyguardAuthenticatedBiometrics: Boolean?)
+
+ suspend fun setKeyguardAuthenticatedPrimaryAuth(userId: Int)
+
+ suspend fun setUserRequestedBouncerWhenAlreadyAuthenticated(userId: Int)
fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean)
@@ -117,9 +141,11 @@
private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
override val primaryBouncerStartingDisappearAnimation =
_primaryBouncerDisappearAnimation.asStateFlow()
+
/** Determines if we want to instantaneously show the primary bouncer instead of translating. */
private val _primaryBouncerScrimmed = MutableStateFlow(false)
override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+
/**
* Set how much of the notification panel is showing on the screen.
*
@@ -134,13 +160,26 @@
override val keyguardPosition = _keyguardPosition.asStateFlow()
private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
- private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
- /** Determines if user is already unlocked */
- override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+
+ /** Whether the user is already unlocked by biometrics */
+ private val _keyguardAuthenticatedBiometrics = MutableStateFlow<Boolean?>(null)
+ override val keyguardAuthenticatedBiometrics = _keyguardAuthenticatedBiometrics.asStateFlow()
+
+ /** Whether the user is unlocked via a primary authentication method (pin/pattern/password). */
+ private val _keyguardAuthenticatedPrimaryAuth = MutableSharedFlow<Int>()
+ override val keyguardAuthenticatedPrimaryAuth: Flow<Int> =
+ _keyguardAuthenticatedPrimaryAuth.asSharedFlow()
+
+ /** Whether the user requested to show the bouncer when device is already authenticated */
+ private val _userRequestedBouncerWhenAlreadyAuthenticated = MutableSharedFlow<Int>()
+ override val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> =
+ _userRequestedBouncerWhenAlreadyAuthenticated.asSharedFlow()
+
private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
override val showMessage = _showMessage.asStateFlow()
private val _resourceUpdateRequests = MutableStateFlow(false)
override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+
/** Values associated with the AlternateBouncer */
private val _alternateBouncerVisible = MutableStateFlow(false)
override val alternateBouncerVisible = _alternateBouncerVisible.asStateFlow()
@@ -204,8 +243,16 @@
_showMessage.value = bouncerShowMessageModel
}
- override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
- _keyguardAuthenticated.value = keyguardAuthenticated
+ override fun setKeyguardAuthenticatedBiometrics(keyguardAuthenticatedBiometrics: Boolean?) {
+ _keyguardAuthenticatedBiometrics.value = keyguardAuthenticatedBiometrics
+ }
+
+ override suspend fun setKeyguardAuthenticatedPrimaryAuth(userId: Int) {
+ _keyguardAuthenticatedPrimaryAuth.emit(userId)
+ }
+
+ override suspend fun setUserRequestedBouncerWhenAlreadyAuthenticated(userId: Int) {
+ _userRequestedBouncerWhenAlreadyAuthenticated.emit(userId)
}
override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 0e0f1f6..579f0b7 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -79,14 +79,18 @@
) {
private val passiveAuthBouncerDelay =
context.resources.getInteger(R.integer.primary_bouncer_passive_auth_delay).toLong()
+
/** Runnable to show the primary bouncer. */
val showRunnable = Runnable {
repository.setPrimaryShow(true)
repository.setPrimaryShowingSoon(false)
primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
}
-
- val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
+ val keyguardAuthenticatedPrimaryAuth: Flow<Int> = repository.keyguardAuthenticatedPrimaryAuth
+ val keyguardAuthenticatedBiometrics: Flow<Boolean> =
+ repository.keyguardAuthenticatedBiometrics.filterNotNull()
+ val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> =
+ repository.userRequestedBouncerWhenAlreadyAuthenticated.filterNotNull()
val isShowing: StateFlow<Boolean> = repository.primaryBouncerShow
val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {}
val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
@@ -96,6 +100,7 @@
val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
val keyguardPosition: Flow<Float> = repository.keyguardPosition.filterNotNull()
val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
+
/** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
val bouncerExpansion: Flow<Float> =
combine(repository.panelExpansionAmount, repository.primaryBouncerShow) {
@@ -107,6 +112,7 @@
0f
}
}
+
/** Allow for interaction when just about fully visible */
val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
@@ -144,7 +150,7 @@
@JvmOverloads
fun show(isScrimmed: Boolean) {
// Reset some states as we show the bouncer.
- repository.setKeyguardAuthenticated(null)
+ repository.setKeyguardAuthenticatedBiometrics(null)
repository.setPrimaryStartingToHide(false)
val resumeBouncer =
@@ -268,9 +274,19 @@
repository.setResourceUpdateRequests(true)
}
- /** Tell the bouncer that keyguard is authenticated. */
- fun notifyKeyguardAuthenticated(strongAuth: Boolean) {
- repository.setKeyguardAuthenticated(strongAuth)
+ /** Tell the bouncer that keyguard is authenticated with primary authentication. */
+ fun notifyKeyguardAuthenticatedPrimaryAuth(userId: Int) {
+ applicationScope.launch { repository.setKeyguardAuthenticatedPrimaryAuth(userId) }
+ }
+
+ /** Tell the bouncer that bouncer is requested when device is already authenticated */
+ fun notifyUserRequestedBouncerWhenAlreadyAuthenticated(userId: Int) {
+ applicationScope.launch { repository.setKeyguardAuthenticatedPrimaryAuth(userId) }
+ }
+
+ /** Tell the bouncer that keyguard is authenticated with biometrics. */
+ fun notifyKeyguardAuthenticatedBiometrics(strongAuth: Boolean) {
+ repository.setKeyguardAuthenticatedBiometrics(strongAuth)
}
/** Update the position of the bouncer when showing. */
@@ -280,7 +296,7 @@
/** Notifies that the state change was handled. */
fun notifyKeyguardAuthenticatedHandled() {
- repository.setKeyguardAuthenticated(null)
+ repository.setKeyguardAuthenticatedBiometrics(null)
}
/** Notifies that the message was shown. */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
index 6ba8439..649ae2f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -59,7 +59,7 @@
val bouncerShowMessage: Flow<BouncerShowMessageModel> = interactor.showMessage
/** Observe whether keyguard is authenticated already. */
- val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticated
+ val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticatedBiometrics
/** Observe whether the side fps is showing. */
val sideFpsShowing: Flow<Boolean> = interactor.sideFpsShowing
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 80a41ce..d214797 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -40,20 +40,21 @@
/** Notifies that the UI has been shown to the user. */
fun onShown() {
+ _password.value = ""
interactor.resetMessage()
}
/** Notifies that the user has changed the password input. */
- fun onPasswordInputChanged(password: String) {
- if (this.password.value.isEmpty() && password.isNotEmpty()) {
+ fun onPasswordInputChanged(newPassword: String) {
+ if (this.password.value.isEmpty() && newPassword.isNotEmpty()) {
interactor.clearMessage()
}
- if (password.isNotEmpty()) {
+ if (newPassword.isNotEmpty()) {
interactor.onIntentionalUserInput()
}
- _password.value = password
+ _password.value = newPassword
}
/** Notifies that the user has pressed the key for attempting to authenticate the password. */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index ebf939b..dc5c528 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -70,13 +70,7 @@
/** Appearance of the confirm button. */
val confirmButtonAppearance: StateFlow<ActionButtonAppearance> =
interactor.isAutoConfirmEnabled
- .map {
- if (it) {
- ActionButtonAppearance.Hidden
- } else {
- ActionButtonAppearance.Shown
- }
- }
+ .map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown }
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
@@ -85,6 +79,7 @@
/** Notifies that the UI has been shown to the user. */
fun onShown() {
+ clearPinInput()
interactor.resetMessage()
}
@@ -113,7 +108,7 @@
/** Notifies that the user long-pressed the backspace button. */
fun onBackspaceButtonLongPressed() {
- mutablePinInput.value = mutablePinInput.value.clearAll()
+ clearPinInput()
}
/** Notifies that the user clicked the "enter" button. */
@@ -121,6 +116,10 @@
tryAuthenticate(useAutoConfirm = false)
}
+ private fun clearPinInput() {
+ mutablePinInput.value = mutablePinInput.value.clearAll()
+ }
+
private fun tryAuthenticate(useAutoConfirm: Boolean) {
val pinCode = mutablePinInput.value.getPin()
@@ -133,7 +132,7 @@
// TODO(b/291528545): this should not be cleared on success (at least until the view
// is animated away).
- mutablePinInput.value = mutablePinInput.value.clearAll()
+ clearPinInput()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index c9517c2..da5e933 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -29,6 +29,7 @@
import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.StatsManager;
+import android.app.StatusBarManager;
import android.app.UiModeManager;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
@@ -681,4 +682,10 @@
static TextClassificationManager provideTextClassificationManager(Context context) {
return context.getSystemService(TextClassificationManager.class);
}
+
+ @Provides
+ @Singleton
+ static StatusBarManager provideStatusBarManager(Context context) {
+ return context.getSystemService(StatusBarManager.class);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 7ce7ce94..f12b919 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -34,9 +34,11 @@
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.keyboard.KeyboardUI
import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable
-import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.KeyguardViewConfigurator
+import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.data.quickaffordance.MuteQuickAffordanceCoreStartable
+import com.android.systemui.keyguard.ui.binder.KeyguardDismissActionBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardDismissBinder
import com.android.systemui.log.SessionTracker
import com.android.systemui.media.RingtonePlayer
import com.android.systemui.media.dialog.MediaOutputSwitcherDialogUI
@@ -74,12 +76,14 @@
/**
* Collection of {@link CoreStartable}s that should be run on AOSP.
*/
-@Module(includes = [
- MultiUserUtilsModule::class,
- StartControlsStartableModule::class,
- StartBinderLoggerModule::class,
- WallpaperModule::class,
-])
+@Module(
+ includes = [
+ MultiUserUtilsModule::class,
+ StartControlsStartableModule::class,
+ StartBinderLoggerModule::class,
+ WallpaperModule::class,
+ ]
+)
abstract class SystemUICoreStartableModule {
/** Inject into AuthController. */
@Binds
@@ -352,4 +356,14 @@
@IntoMap
@ClassKey(BackActionInteractor::class)
abstract fun bindBackActionInteractor(impl: BackActionInteractor): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(KeyguardDismissActionBinder::class)
+ abstract fun bindKeyguardDismissActionBinder(impl: KeyguardDismissActionBinder): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(KeyguardDismissBinder::class)
+ abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/DisplayEvent.kt b/packages/SystemUI/src/com/android/systemui/display/data/DisplayEvent.kt
new file mode 100644
index 0000000..626a68f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/DisplayEvent.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data
+
+sealed interface DisplayEvent {
+ val displayId: Int
+ data class Added(override val displayId: Int) : DisplayEvent
+ data class Removed(override val displayId: Int) : DisplayEvent
+ data class Changed(override val displayId: Int) : DisplayEvent
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 3444af4..1751358 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -29,6 +29,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.DisplayEvent
import com.android.systemui.util.Compile
import com.android.systemui.util.traceSection
import javax.inject.Inject
@@ -41,6 +42,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -48,7 +50,13 @@
/** Provides a [Flow] of [Display] as returned by [DisplayManager]. */
interface DisplayRepository {
- /** Provides a nullable set of displays. */
+ /** Display change event indicating a change to the given displayId has occurred. */
+ val displayChangeEvent: Flow<Int>
+
+ /**
+ * Provides a nullable set of displays. Updates when new displays have been added or removed but
+ * not when a display's info has changed.
+ */
val displays: Flow<Set<Display>>
/**
@@ -86,33 +94,36 @@
@Application applicationScope: CoroutineScope,
@Background backgroundCoroutineDispatcher: CoroutineDispatcher
) : DisplayRepository {
-
// Displays are enabled only after receiving them in [onDisplayAdded]
- private val enabledDisplays: StateFlow<Set<Display>> =
- conflatedCallbackFlow {
- val callback =
- object : DisplayListener {
- override fun onDisplayAdded(displayId: Int) {
- trySend(getDisplays())
- }
+ private val allDisplayEvents: Flow<DisplayEvent> = conflatedCallbackFlow {
+ val callback =
+ object : DisplayListener {
+ override fun onDisplayAdded(displayId: Int) {
+ trySend(DisplayEvent.Added(displayId))
+ }
- override fun onDisplayRemoved(displayId: Int) {
- trySend(getDisplays())
- }
+ override fun onDisplayRemoved(displayId: Int) {
+ trySend(DisplayEvent.Removed(displayId))
+ }
- override fun onDisplayChanged(displayId: Int) {
- trySend(getDisplays())
- }
- }
- displayManager.registerDisplayListener(
- callback,
- backgroundHandler,
- EVENT_FLAG_DISPLAY_ADDED or
- EVENT_FLAG_DISPLAY_CHANGED or
- EVENT_FLAG_DISPLAY_REMOVED,
- )
- awaitClose { displayManager.unregisterDisplayListener(callback) }
+ override fun onDisplayChanged(displayId: Int) {
+ trySend(DisplayEvent.Changed(displayId))
+ }
}
+ displayManager.registerDisplayListener(
+ callback,
+ backgroundHandler,
+ EVENT_FLAG_DISPLAY_ADDED or EVENT_FLAG_DISPLAY_CHANGED or EVENT_FLAG_DISPLAY_REMOVED,
+ )
+ awaitClose { displayManager.unregisterDisplayListener(callback) }
+ }
+
+ override val displayChangeEvent: Flow<Int> =
+ allDisplayEvents.filter { it is DisplayEvent.Changed }.map { it.displayId }
+
+ private val enabledDisplays =
+ allDisplayEvents
+ .map { getDisplays() }
.flowOn(backgroundCoroutineDispatcher)
.stateIn(
applicationScope,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 1cd3774..4e4b79c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -155,9 +155,12 @@
return true;
}
- // Don't set expansion if the user doesn't have a pin/password set.
+ // Don't set expansion if the user doesn't have a pin/password set so that no
+ // animations are played we're not transitioning to the bouncer.
if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
- return true;
+ // Return false so the gesture is not consumed, allowing the dream to wake
+ // if it wants instead of doing nothing.
+ return false;
}
// For consistency, we adopt the expansion definition found in the
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 2cc07fd..c29664e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -268,10 +268,6 @@
val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA =
unreleasedFlag("migrate_split_keyguard_bottom_area", teamfood = true)
- /** Whether to listen for fingerprint authentication over keyguard occluding activities. */
- // TODO(b/283260512): Tracking bug.
- @JvmField val FP_LISTEN_OCCLUDING_APPS = releasedFlag("fp_listen_occluding_apps")
-
/** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */
// TODO(b/286563884): Tracking bug
@JvmField val KEYGUARD_TALKBACK_FIX = releasedFlag("keyguard_talkback_fix")
@@ -312,11 +308,6 @@
val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag =
unreleasedFlag("keyguard_wm_state_refactor")
- /** Stop running face auth when the display state changes to OFF. */
- // TODO(b/294221702): Tracking bug.
- @JvmField val STOP_FACE_AUTH_ON_DISPLAY_OFF = resourceBooleanFlag(
- R.bool.flag_stop_face_auth_on_display_off, "stop_face_auth_on_display_off")
-
/** Flag to disable the face scanning animation pulsing. */
// TODO(b/295245791): Tracking bug.
@JvmField val STOP_PULSING_FACE_SCANNING_ANIMATION = resourceBooleanFlag(
@@ -365,6 +356,10 @@
@JvmField
val QS_PIPELINE_AUTO_ADD = unreleasedFlag("qs_pipeline_auto_add", teamfood = true)
+ // TODO(b/296357483): Tracking Bug
+ @JvmField
+ val QS_PIPELINE_NEW_TILES = unreleasedFlag("qs_pipeline_new_tiles")
+
// TODO(b/254512383): Tracking Bug
@JvmField
val FULL_SCREEN_USER_SWITCHER =
@@ -389,9 +384,6 @@
// TODO(b/265892345): Tracking Bug
val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip")
- // TODO(b/280426085): Tracking Bug
- @JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag("new_bluetooth_repository")
-
// TODO(b/292533677): Tracking Bug
val WIFI_TRACKER_LIB_FOR_WIFI_ICON = releasedFlag("wifi_tracker_lib_for_wifi_icon")
@@ -785,7 +777,8 @@
/** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
@JvmField
- val ENABLE_CLOCK_KEYGUARD_PRESENTATION = unreleasedFlag("enable_clock_keyguard_presentation")
+ val ENABLE_CLOCK_KEYGUARD_PRESENTATION =
+ unreleasedFlag("enable_clock_keyguard_presentation", teamfood = true)
/** Enable the Compose implementation of the PeopleSpaceActivity. */
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 8954947..6db21b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -26,6 +26,7 @@
import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.Dumpable
import com.android.systemui.R
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -50,7 +51,6 @@
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
@@ -66,9 +66,10 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
@@ -77,6 +78,7 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -153,13 +155,14 @@
private val faceAuthLogger: FaceAuthenticationLogger,
private val biometricSettingsRepository: BiometricSettingsRepository,
private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
- private val trustRepository: TrustRepository,
+ trustRepository: TrustRepository,
private val keyguardRepository: KeyguardRepository,
private val keyguardInteractor: KeyguardInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
@FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
@FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val displayStateInteractor: DisplayStateInteractor,
private val featureFlags: FeatureFlags,
dumpManager: DumpManager,
) : DeviceEntryFaceAuthRepository, Dumpable {
@@ -202,11 +205,9 @@
private val keyguardSessionId: InstanceId?
get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
- private val _canRunFaceAuth = MutableStateFlow(false)
override val canRunFaceAuth: StateFlow<Boolean>
- get() = _canRunFaceAuth
- private val canRunDetection = MutableStateFlow(false)
+ private val canRunDetection: StateFlow<Boolean>
private val _isAuthenticated = MutableStateFlow(false)
override val isAuthenticated: Flow<Boolean>
@@ -252,10 +253,58 @@
dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this)
if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
+ canRunFaceAuth =
+ listOf(
+ *gatingConditionsForAuthAndDetect(),
+ Pair(isLockedOut.isFalse(), "isNotInLockOutState"),
+ Pair(
+ trustRepository.isCurrentUserTrusted.isFalse(),
+ "currentUserIsNotTrusted"
+ ),
+ Pair(
+ biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
+ "isFaceAuthCurrentlyAllowed"
+ ),
+ Pair(isAuthenticated.isFalse(), "faceNotAuthenticated"),
+ )
+ .andAllFlows("canFaceAuthRun", faceAuthLog)
+ .flowOn(mainDispatcher)
+ .stateIn(applicationScope, SharingStarted.Eagerly, false)
+
+ // Face detection can run only when lockscreen bypass is enabled
+ // & detection is supported
+ // & biometric unlock is not allowed
+ // or user is trusted by trust manager & we want to run face detect to dismiss
+ // keyguard
+ canRunDetection =
+ listOf(
+ *gatingConditionsForAuthAndDetect(),
+ Pair(isBypassEnabled, "isBypassEnabled"),
+ Pair(
+ biometricSettingsRepository.isFaceAuthCurrentlyAllowed
+ .isFalse()
+ .or(trustRepository.isCurrentUserTrusted),
+ "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted"
+ ),
+ // We don't want to run face detect if fingerprint can be used to unlock the
+ // device
+ // but it's not possible to authenticate with FP from the bouncer (UDFPS)
+ Pair(
+ and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning)
+ .isFalse(),
+ "udfpsAuthIsNotPossibleAnymore"
+ )
+ )
+ .andAllFlows("canFaceDetectRun", faceDetectLog)
+ .flowOn(mainDispatcher)
+ .stateIn(applicationScope, SharingStarted.Eagerly, false)
observeFaceAuthGatingChecks()
observeFaceDetectGatingChecks()
observeFaceAuthResettingConditions()
listenForSchedulingWatchdog()
+ } else {
+ canRunFaceAuth = MutableStateFlow(false).asStateFlow()
+ canRunDetection = MutableStateFlow(false).asStateFlow()
}
}
@@ -298,39 +347,13 @@
}
private fun observeFaceDetectGatingChecks() {
- // Face detection can run only when lockscreen bypass is enabled
- // & detection is supported
- // & biometric unlock is not allowed
- // or user is trusted by trust manager & we want to run face detect to dismiss keyguard
- listOf(
- canFaceAuthOrDetectRun(faceDetectLog),
- logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
- logAndObserve(
- biometricSettingsRepository.isFaceAuthCurrentlyAllowed
- .isFalse()
- .or(trustRepository.isCurrentUserTrusted),
- "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted",
- faceDetectLog
- ),
- // We don't want to run face detect if fingerprint can be used to unlock the device
- // but it's not possible to authenticate with FP from the bouncer (UDFPS)
- logAndObserve(
- and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(),
- "udfpsAuthIsNotPossibleAnymore",
- faceDetectLog
- )
- )
- .reduce(::and)
- .distinctUntilChanged()
+ canRunDetection
.onEach {
- faceAuthLogger.canRunDetectionChanged(it)
- canRunDetection.value = it
if (!it) {
cancelDetection()
}
}
.flowOn(mainDispatcher)
- .logDiffsForTable(faceDetectLog, "", "canFaceDetectRun", false)
.launchIn(applicationScope)
}
@@ -339,76 +362,54 @@
it == BiometricType.UNDER_DISPLAY_FINGERPRINT
}
- private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> {
- return listOf(
- logAndObserve(
- biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
- "isFaceAuthEnrolledAndEnabled",
- tableLogBuffer
- ),
- logAndObserve(faceAuthPaused.isFalse(), "faceAuthIsNotPaused", tableLogBuffer),
- logAndObserve(
- keyguardRepository.isKeyguardGoingAway.isFalse(),
- "keyguardNotGoingAway",
- tableLogBuffer
- ),
- logAndObserve(
- keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(),
- "deviceNotStartingToSleep",
- tableLogBuffer
- ),
- logAndObserve(
- keyguardInteractor.isSecureCameraActive
- .isFalse()
- .or(
- alternateBouncerInteractor.isVisible.or(
- keyguardInteractor.primaryBouncerShowing
- )
- ),
- "secureCameraNotActiveOrAnyBouncerIsShowing",
- tableLogBuffer
- ),
- logAndObserve(
- biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
- "isFaceAuthSupportedInCurrentPosture",
- tableLogBuffer
- ),
- logAndObserve(
- biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
- "userHasNotLockedDownDevice",
- tableLogBuffer
- ),
- logAndObserve(
- keyguardRepository.isKeyguardShowing,
- "isKeyguardShowing",
- tableLogBuffer
- )
- )
- .reduce(::and)
+ private fun gatingConditionsForAuthAndDetect(): Array<Pair<Flow<Boolean>, String>> {
+ return arrayOf(
+ Pair(
+ and(
+ displayStateInteractor.isDefaultDisplayOff,
+ keyguardRepository.wakefulness.map { it.isAwake() },
+ )
+ .isFalse(),
+ // this can happen if an app is requesting for screen off, the display can
+ // turn off without wakefulness.isStartingToSleepOrAsleep calls
+ "displayIsNotOffWhileAwake",
+ ),
+ Pair(
+ biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
+ "isFaceAuthEnrolledAndEnabled"
+ ),
+ Pair(faceAuthPaused.isFalse(), "faceAuthIsNotPaused"),
+ Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
+ Pair(
+ keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(),
+ "deviceNotStartingToSleep"
+ ),
+ Pair(
+ keyguardInteractor.isSecureCameraActive
+ .isFalse()
+ .or(
+ alternateBouncerInteractor.isVisible.or(
+ keyguardInteractor.primaryBouncerShowing
+ )
+ ),
+ "secureCameraNotActiveOrAnyBouncerIsShowing"
+ ),
+ Pair(
+ biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
+ "isFaceAuthSupportedInCurrentPosture"
+ ),
+ Pair(
+ biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
+ "userHasNotLockedDownDevice"
+ ),
+ Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing")
+ )
}
private fun observeFaceAuthGatingChecks() {
- // Face auth can run only if all of the gating conditions are true.
- listOf(
- canFaceAuthOrDetectRun(faceAuthLog),
- logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog),
- logAndObserve(
- trustRepository.isCurrentUserTrusted.isFalse(),
- "currentUserIsNotTrusted",
- faceAuthLog
- ),
- logAndObserve(
- biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
- "isFaceAuthCurrentlyAllowed",
- faceAuthLog
- ),
- logAndObserve(isAuthenticated.isFalse(), "faceNotAuthenticated", faceAuthLog),
- )
- .reduce(::and)
- .distinctUntilChanged()
+ canRunFaceAuth
.onEach {
faceAuthLogger.canFaceAuthRunChanged(it)
- _canRunFaceAuth.value = it
if (!it) {
// Cancel currently running auth if any of the gating checks are false.
faceAuthLogger.cancellingFaceAuth()
@@ -416,7 +417,6 @@
}
}
.flowOn(mainDispatcher)
- .logDiffsForTable(faceAuthLog, "", "canFaceAuthRun", false)
.launchIn(applicationScope)
}
@@ -618,22 +618,6 @@
_isAuthRunning.value = false
}
- private fun logAndObserve(
- cond: Flow<Boolean>,
- conditionName: String,
- logBuffer: TableLogBuffer
- ): Flow<Boolean> {
- return cond
- .distinctUntilChanged()
- .logDiffsForTable(
- logBuffer,
- columnName = conditionName,
- columnPrefix = "",
- initialValue = false
- )
- .onEach { faceAuthLogger.observedConditionChanged(it, conditionName) }
- }
-
companion object {
const val TAG = "DeviceEntryFaceAuthRepository"
@@ -688,3 +672,18 @@
private fun Flow<Boolean>.isFalse(): Flow<Boolean> {
return this.map { !it }
}
+
+private fun List<Pair<Flow<Boolean>, String>>.andAllFlows(
+ combinedLoggingInfo: String,
+ tableLogBuffer: TableLogBuffer
+): Flow<Boolean> {
+ return combine(this.map { it.first }) {
+ val combinedValue =
+ it.reduceIndexed { index, accumulator, current ->
+ tableLogBuffer.logChange(prefix = "", columnName = this[index].second, current)
+ return@reduceIndexed accumulator && current
+ }
+ tableLogBuffer.logChange(prefix = "", combinedLoggingInfo, combinedValue)
+ return@combine combinedValue
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index f91ae74..f5ef27d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule
import java.io.PrintWriter
+import java.util.TreeMap
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -52,11 +53,12 @@
blueprints: Set<@JvmSuppressWildcards KeyguardBlueprint>,
@Application private val applicationScope: CoroutineScope,
) {
- private val blueprintIdMap: Map<String, KeyguardBlueprint> = blueprints.associateBy { it.id }
+ private val blueprintIdMap: TreeMap<String, KeyguardBlueprint> = TreeMap()
private val _blueprint: MutableSharedFlow<KeyguardBlueprint> = MutableSharedFlow(replay = 1)
val blueprint: Flow<KeyguardBlueprint> = _blueprint.asSharedFlow()
init {
+ blueprintIdMap.putAll(blueprints.associateBy { it.id })
applyBlueprint(blueprintIdMap[DEFAULT]!!)
applicationScope.launch {
configurationRepository.onAnyConfigurationChange.collect { refreshBlueprint() }
@@ -69,6 +71,20 @@
* @param blueprintId
* @return whether the transition has succeeded.
*/
+ fun applyBlueprint(index: Int): Boolean {
+ ArrayList(blueprintIdMap.values)[index]?.let {
+ applyBlueprint(it)
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Emits the blueprint value to the collectors.
+ *
+ * @param blueprintId
+ * @return whether the transition has succeeded.
+ */
fun applyBlueprint(blueprintId: String?): Boolean {
val blueprint = blueprintIdMap[blueprintId] ?: return false
applyBlueprint(blueprint)
@@ -89,6 +105,6 @@
/** Prints all available blueprints to the PrintWriter. */
fun printBlueprints(pw: PrintWriter) {
- blueprintIdMap.forEach { entry -> pw.println("${entry.key}") }
+ blueprintIdMap.onEachIndexed { index, entry -> pw.println("$index: ${entry.key}") }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index d399e4c..2557e81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -35,8 +35,10 @@
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
import com.android.systemui.keyguard.shared.model.ScreenModel
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -53,9 +55,11 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@@ -187,6 +191,12 @@
/** Receive an event for doze time tick */
val dozeTimeTick: Flow<Long>
+ /** Observable for DismissAction */
+ val dismissAction: StateFlow<DismissAction>
+
+ /** Observable updated when keyguardDone should be called either now or soon. */
+ val keyguardDone: Flow<KeyguardDone>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -239,6 +249,10 @@
fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean)
fun dozeTimeTick()
+
+ fun setDismissAction(dismissAction: DismissAction)
+
+ suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone)
}
/** Encapsulates application state for the keyguard. */
@@ -261,6 +275,19 @@
@Application private val scope: CoroutineScope,
private val systemClock: SystemClock,
) : KeyguardRepository {
+ private val _dismissAction: MutableStateFlow<DismissAction> =
+ MutableStateFlow(DismissAction.None)
+ override val dismissAction = _dismissAction.asStateFlow()
+ override fun setDismissAction(dismissAction: DismissAction) {
+ _dismissAction.value = dismissAction
+ }
+
+ private val _keyguardDone: MutableSharedFlow<KeyguardDone> = MutableSharedFlow()
+ override val keyguardDone = _keyguardDone.asSharedFlow()
+ override suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone) {
+ _keyguardDone.emit(keyguardDoneType)
+ }
+
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
_animateBottomAreaDozingTransitions.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
index 867675b..00036ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.repository
import android.app.trust.TrustManager
+import com.android.keyguard.TrustGrantFlags
import com.android.keyguard.logging.TrustRepositoryLogger
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -34,6 +35,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
@@ -50,6 +52,9 @@
/** Reports that whether trust is managed has changed for the current user. */
val isCurrentUserTrustManaged: StateFlow<Boolean>
+
+ /** A trust agent is requesting to dismiss the keyguard from a trust change. */
+ val trustAgentRequestingToDismissKeyguard: Flow<TrustModel>
}
@SysUISingleton
@@ -78,7 +83,7 @@
) {
logger.onTrustChanged(enabled, newlyUnlocked, userId, flags, grantMsgs)
trySendWithFailureLogging(
- TrustModel(enabled, userId),
+ TrustModel(enabled, userId, TrustGrantFlags(flags)),
TrustRepositoryLogger.TAG,
"onTrustChanged"
)
@@ -158,6 +163,17 @@
initialValue = false
)
+ override val trustAgentRequestingToDismissKeyguard: Flow<TrustModel>
+ get() =
+ combine(trust, userRepository.selectedUserInfo, ::Pair)
+ .map { latestTrustModelForUser[it.second.id] }
+ .distinctUntilChanged()
+ .filter {
+ it != null &&
+ (it.flags.isInitiatedByUser || it.flags.dismissKeyguardRequested())
+ }
+ .map { it!! }
+
private fun isUserTrustManaged(userId: Int) =
trustManagedForUser[userId]?.isTrustManaged ?: false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index 390ad7e..6ce9185 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -37,6 +37,16 @@
return keyguardBlueprintRepository.applyBlueprint(blueprintId)
}
+ /**
+ * Transitions to a blueprint.
+ *
+ * @param blueprintId
+ * @return whether the transition has succeeded.
+ */
+ fun transitionToBlueprint(blueprintId: Int): Boolean {
+ return keyguardBlueprintRepository.applyBlueprint(blueprintId)
+ }
+
/** Re-emits the blueprint value to the collectors. */
fun refreshBlueprint() {
keyguardBlueprintRepository.refreshBlueprint()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
new file mode 100644
index 0000000..08d29d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.DismissAction
+import com.android.systemui.keyguard.shared.model.KeyguardDone
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
+
+/** Encapsulates business-logic for actions to run when the keyguard is dismissed. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class KeyguardDismissActionInteractor
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+ transitionInteractor: KeyguardTransitionInteractor,
+ val dismissInteractor: KeyguardDismissInteractor,
+ @Application private val applicationScope: CoroutineScope,
+) {
+ val dismissAction: Flow<DismissAction> = repository.dismissAction
+
+ val onCancel: Flow<Runnable> = dismissAction.map { it.onCancelAction }
+
+ // TODO (b/268240415): use message in alt + primary bouncer message
+ // message to show to the user about the dismiss action, else empty string
+ val message = dismissAction.map { it.message }
+
+ /**
+ * True if the dismiss action will run an animation on the lockscreen and requires any views
+ * that would obscure this animation (ie: the primary bouncer) to immediately hide, so the
+ * animation would be visible.
+ */
+ val willAnimateDismissActionOnLockscreen: StateFlow<Boolean> =
+ dismissAction
+ .map { it.willAnimateOnLockscreen }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ private val finishedTransitionToGone: Flow<Unit> =
+ transitionInteractor.finishedKeyguardState.filter { it == GONE }.map {} // map to Unit
+ val executeDismissAction: Flow<() -> KeyguardDone> =
+ merge(
+ finishedTransitionToGone,
+ dismissInteractor.dismissKeyguardRequestWithImmediateDismissAction
+ )
+ .sample(dismissAction)
+ .filterNot { it is DismissAction.None }
+ .map { it.onDismissAction }
+ val resetDismissAction: Flow<Unit> =
+ transitionInteractor.finishedKeyguardTransitionStep
+ .filter { it.to != ALTERNATE_BOUNCER && it.to != PRIMARY_BOUNCER && it.to != GONE }
+ .sample(dismissAction)
+ .filterNot { it is DismissAction.None }
+ .map {} // map to Unit
+
+ fun runDismissAnimationOnKeyguard(): Boolean {
+ return willAnimateDismissActionOnLockscreen.value
+ }
+
+ fun runAfterKeyguardGone(runnable: Runnable) {
+ setDismissAction(
+ DismissAction.RunAfterKeyguardGone(
+ dismissAction = { runnable.run() },
+ onCancelAction = {},
+ message = "",
+ willAnimateOnLockscreen = false,
+ )
+ )
+ }
+
+ fun setDismissAction(dismissAction: DismissAction) {
+ repository.dismissAction.value.onCancelAction.run()
+ repository.setDismissAction(dismissAction)
+ }
+
+ fun handleDismissAction() {
+ repository.setDismissAction(DismissAction.None)
+ }
+
+ suspend fun setKeyguardDone(keyguardDoneTiming: KeyguardDone) {
+ dismissInteractor.setKeyguardDone(keyguardDoneTiming)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
new file mode 100644
index 0000000..cab6928
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.keyguard.shared.model.DismissAction
+import com.android.systemui.keyguard.shared.model.KeyguardDone
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.toQuad
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** Encapsulates business logic for requesting the keyguard to dismiss/finish/done. */
+@SysUISingleton
+class KeyguardDismissInteractor
+@Inject
+constructor(
+ trustRepository: TrustRepository,
+ val keyguardRepository: KeyguardRepository,
+ val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ val alternateBouncerInteractor: AlternateBouncerInteractor,
+ val powerInteractor: PowerInteractor,
+ val userInteractor: UserInteractor,
+) {
+ /*
+ * Updates when a biometric has authenticated the device and is requesting to dismiss
+ * the keyguard. When true, a class 3 biometrics has authenticated. Else, a lower class
+ * biometric strength has authenticated and is requesting to dismiss the keyguard.
+ */
+ private val biometricAuthenticatedRequestDismissKeyguard: Flow<Unit> =
+ primaryBouncerInteractor.keyguardAuthenticatedBiometrics.map {} // map to Unit
+
+ /*
+ * Updates when a trust change is requesting to dismiss the keyguard and is able to do so
+ * in the current device state.
+ */
+ private val onTrustGrantedRequestDismissKeyguard: Flow<Unit> =
+ trustRepository.trustAgentRequestingToDismissKeyguard
+ .sample(
+ combine(
+ primaryBouncerInteractor.isShowing,
+ alternateBouncerInteractor.isVisible,
+ powerInteractor.isInteractive,
+ ::Triple
+ ),
+ ::toQuad
+ )
+ .filter { (trustModel, primaryBouncerShowing, altBouncerShowing, interactive) ->
+ val bouncerShowing = primaryBouncerShowing || altBouncerShowing
+ (interactive || trustModel.flags.temporaryAndRenewable()) &&
+ (bouncerShowing || trustModel.flags.dismissKeyguardRequested())
+ }
+ .map {} // map to Unit
+
+ /*
+ * Updates when the current user successfully has authenticated via primary authentication
+ * (pin/pattern/password).
+ */
+ private val primaryAuthenticated: Flow<Unit> =
+ primaryBouncerInteractor.keyguardAuthenticatedPrimaryAuth
+ .filter { authedUserId -> authedUserId == userInteractor.getSelectedUserId() }
+ .map {} // map to Unit
+
+ /*
+ * Updates when the current user requests the bouncer after they've already successfully
+ * authenticated (ie: from non-bypass face auth, from a trust agent that didn't immediately
+ * dismiss the keyguard, or if keyguard security is set to SWIPE or NONE).
+ */
+ private val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Unit> =
+ primaryBouncerInteractor.userRequestedBouncerWhenAlreadyAuthenticated
+ .filter { authedUserId -> authedUserId == userInteractor.getSelectedUserId() }
+ .map {} // map to Unit
+
+ /** Updates when keyguardDone should be requested. */
+ val keyguardDone: Flow<KeyguardDone> = keyguardRepository.keyguardDone
+
+ /** Updates when any request to dismiss the current user's keyguard has arrived. */
+ private val dismissKeyguardRequest: Flow<DismissAction> =
+ merge(
+ biometricAuthenticatedRequestDismissKeyguard,
+ onTrustGrantedRequestDismissKeyguard,
+ primaryAuthenticated,
+ userRequestedBouncerWhenAlreadyAuthenticated,
+ )
+ .sample(keyguardRepository.dismissAction)
+
+ /**
+ * Updates when a request to dismiss the current user's keyguard has arrived and there's a
+ * dismiss action to run immediately. It's expected that the consumer will request keyguardDone
+ * with or without a deferral.
+ */
+ val dismissKeyguardRequestWithImmediateDismissAction: Flow<Unit> =
+ dismissKeyguardRequest.filter { it is DismissAction.RunImmediately }.map {} // map to Unit
+
+ /**
+ * Updates when a request to dismiss the current user's keyguard has arrived and there's isn't a
+ * dismiss action to run immediately. There may still be a dismiss action to run after the
+ * keyguard transitions to GONE.
+ */
+ val dismissKeyguardRequestWithoutImmediateDismissAction: Flow<Unit> =
+ dismissKeyguardRequest.filter { it !is DismissAction.RunImmediately }.map {} // map to Unit
+
+ suspend fun setKeyguardDone(keyguardDoneTiming: KeyguardDone) {
+ keyguardRepository.setKeyguardDone(keyguardDoneTiming)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index 3ec660a..f6ad829 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -22,8 +22,6 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
@@ -60,7 +58,6 @@
private val context: Context,
activityStarter: ActivityStarter,
powerInteractor: PowerInteractor,
- featureFlags: FeatureFlags,
) {
private val keyguardOccludedByApp: Flow<Boolean> =
combine(
@@ -93,37 +90,35 @@
.ifKeyguardOccludedByApp(/* elseFlow */ flowOf(null))
init {
- if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
- scope.launch {
- // On fingerprint success when the screen is on, go to the home screen
- fingerprintUnlockSuccessEvents.sample(powerInteractor.isInteractive).collect {
- if (it) {
- goToHomeScreen()
- }
- // don't go to the home screen if the authentication is from AOD/dozing/off
+ scope.launch {
+ // On fingerprint success when the screen is on, go to the home screen
+ fingerprintUnlockSuccessEvents.sample(powerInteractor.isInteractive).collect {
+ if (it) {
+ goToHomeScreen()
}
+ // don't go to the home screen if the authentication is from AOD/dozing/off
}
+ }
- scope.launch {
- // On device fingerprint lockout, request the bouncer with a runnable to
- // go to the home screen. Without this, the bouncer won't proceed to the home
- // screen.
- fingerprintLockoutEvents.collect {
- activityStarter.dismissKeyguardThenExecute(
- object : ActivityStarter.OnDismissAction {
- override fun onDismiss(): Boolean {
- goToHomeScreen()
- return false
- }
+ scope.launch {
+ // On device fingerprint lockout, request the bouncer with a runnable to
+ // go to the home screen. Without this, the bouncer won't proceed to the home
+ // screen.
+ fingerprintLockoutEvents.collect {
+ activityStarter.dismissKeyguardThenExecute(
+ object : ActivityStarter.OnDismissAction {
+ override fun onDismiss(): Boolean {
+ goToHomeScreen()
+ return false
+ }
- override fun willRunAnimationOnKeyguard(): Boolean {
- return false
- }
- },
- /* cancel= */ null,
- /* afterKeyguardGone */ false
- )
- }
+ override fun willRunAnimationOnKeyguard(): Boolean {
+ return false
+ }
+ },
+ /* cancel= */ null,
+ /* afterKeyguardGone */ false
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DismissAction.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DismissAction.kt
new file mode 100644
index 0000000..c8d5599
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DismissAction.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/** DismissAction models */
+sealed interface DismissAction {
+ val onDismissAction: () -> KeyguardDone
+ val onCancelAction: Runnable
+ val message: String
+ /**
+ * True if the dismiss action will run an animation on the keyguard and requires any views that
+ * would obscure this animation (ie: the primary bouncer) to immediately hide, so the animation
+ * would be visible.
+ */
+ val willAnimateOnLockscreen: Boolean
+ val runAfterKeyguardGone: Boolean
+
+ class RunImmediately(
+ override val onDismissAction: () -> KeyguardDone,
+ override val onCancelAction: Runnable,
+ override val message: String,
+ override val willAnimateOnLockscreen: Boolean,
+ ) : DismissAction {
+ override val runAfterKeyguardGone: Boolean = false
+ }
+
+ class RunAfterKeyguardGone(
+ val dismissAction: () -> Unit,
+ override val onCancelAction: Runnable,
+ override val message: String,
+ override val willAnimateOnLockscreen: Boolean,
+ ) : DismissAction {
+ override val onDismissAction: () -> KeyguardDone = {
+ dismissAction()
+ // no-op, when this dismissAction is run after the keyguard is gone,
+ // the keyguard is already done so KeyguardDone timing is irrelevant
+ KeyguardDone.IMMEDIATE
+ }
+ override val runAfterKeyguardGone: Boolean = true
+ }
+
+ data object None : DismissAction {
+ override val onDismissAction: () -> KeyguardDone = { KeyguardDone.IMMEDIATE }
+ override val onCancelAction: Runnable = Runnable {}
+ override val message: String = ""
+ override val willAnimateOnLockscreen: Boolean = false
+ override val runAfterKeyguardGone: Boolean = false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardDone.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardDone.kt
new file mode 100644
index 0000000..5e8cf57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardDone.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/**
+ * When to send the keyguard done signal. Should it immediately be sent when the keyguard is
+ * requested to be dismissed? Or should it be sent later?
+ */
+enum class KeyguardDone {
+ IMMEDIATE, // keyguard is immediately done, immediately start transitioning away keyguard
+ LATER, // keyguard is dismissible pending the next keyguardDone call
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
index cdfab1a..206b792 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.shared.model
+import com.android.keyguard.TrustGrantFlags
+
sealed class TrustMessage
/** Represents the trust state */
@@ -24,6 +26,7 @@
val isTrusted: Boolean,
/** The user, for which the trust changed. */
val userId: Int,
+ val flags: TrustGrantFlags,
) : TrustMessage()
/** Represents where trust agents are enabled for a particular user. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
index 62f43ed..2a5beaf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -36,7 +36,7 @@
private fun isAsleep() = state == ASLEEP
- private fun isAwake() = state == AWAKE
+ fun isAwake() = state == AWAKE
fun isStartingToWakeOrAwake() = isStartingToWake() || isAwake()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
new file mode 100644
index 0000000..d5add61
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.ui.binder
+
+import com.android.keyguard.logging.KeyguardLogger
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+/** Runs actions on keyguard dismissal. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardDismissActionBinder
+@Inject
+constructor(
+ private val interactor: KeyguardDismissActionInteractor,
+ @Application private val scope: CoroutineScope,
+ private val keyguardLogger: KeyguardLogger,
+ private val featureFlags: FeatureFlagsClassic,
+) : CoreStartable {
+
+ override fun start() {
+ if (!featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ return
+ }
+
+ scope.launch {
+ interactor.executeDismissAction.collect {
+ log("executeDismissAction")
+ interactor.setKeyguardDone(it())
+ interactor.handleDismissAction()
+ }
+ }
+
+ scope.launch {
+ interactor.resetDismissAction.sample(interactor.onCancel).collect {
+ log("resetDismissAction")
+ it.run()
+ interactor.handleDismissAction()
+ }
+ }
+
+ scope.launch { interactor.dismissAction.collect { log("updatedDismissAction=$it") } }
+ }
+
+ private fun log(message: String) {
+ keyguardLogger.log(TAG, LogLevel.DEBUG, message)
+ }
+
+ companion object {
+ private const val TAG = "KeyguardDismissAction"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
new file mode 100644
index 0000000..f14552b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.ui.binder
+
+import com.android.keyguard.ViewMediatorCallback
+import com.android.keyguard.logging.KeyguardLogger
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardDone
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.user.domain.interactor.UserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+/** Handles keyguard dismissal requests. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardDismissBinder
+@Inject
+constructor(
+ private val interactor: KeyguardDismissInteractor,
+ private val userInteractor: UserInteractor,
+ private val viewMediatorCallback: ViewMediatorCallback,
+ @Application private val scope: CoroutineScope,
+ private val keyguardLogger: KeyguardLogger,
+ private val featureFlags: FeatureFlagsClassic,
+) : CoreStartable {
+
+ override fun start() {
+ if (!featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ return
+ }
+
+ scope.launch {
+ interactor.keyguardDone.collect { keyguardDoneTiming ->
+ when (keyguardDoneTiming) {
+ KeyguardDone.LATER -> {
+ log("keyguardDonePending")
+ viewMediatorCallback.keyguardDonePending(userInteractor.getSelectedUserId())
+ }
+ else -> {
+ log("keyguardDone")
+ viewMediatorCallback.keyguardDone(userInteractor.getSelectedUserId())
+ }
+ }
+ }
+ }
+
+ scope.launch {
+ interactor.dismissKeyguardRequestWithoutImmediateDismissAction.collect {
+ log("dismissKeyguardRequestWithoutImmediateDismissAction-keyguardDone")
+ interactor.setKeyguardDone(KeyguardDone.IMMEDIATE)
+ }
+ }
+ }
+
+ private fun log(message: String) {
+ keyguardLogger.log(TAG, LogLevel.DEBUG, message)
+ }
+
+ companion object {
+ private const val TAG = "KeyguardDismiss"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 9503f2c..4b76821 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -59,20 +59,18 @@
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
- launch {
- occludingAppDeviceEntryMessageViewModel.message.collect {
- biometricMessage ->
- if (biometricMessage?.message != null) {
- chipbarCoordinator.displayView(
- createChipbarInfo(
- biometricMessage.message,
- R.drawable.ic_lock,
- )
+ launch {
+ occludingAppDeviceEntryMessageViewModel.message.collect { biometricMessage
+ ->
+ if (biometricMessage?.message != null) {
+ chipbarCoordinator.displayView(
+ createChipbarInfo(
+ biometricMessage.message,
+ R.drawable.ic_lock,
)
- } else {
- chipbarCoordinator.removeView(ID, "occludingAppMsgNull")
- }
+ )
+ } else {
+ chipbarCoordinator.removeView(ID, "occludingAppMsgNull")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 8945318..85a2fd5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -264,14 +264,8 @@
KeyguardPreviewSmartspaceViewModel.getLargeClockSmartspaceTopPadding(
context.resources,
)
-
val startPadding: Int =
- with(context.resources) {
- getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.clock_padding_start
- ) + getDimensionPixelSize(R.dimen.below_clock_padding_start)
- }
-
+ context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start)
val endPadding: Int =
context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
index 36d21f1..ce7ec0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.view.layout
+import androidx.core.text.isDigitsOnly
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.statusbar.commandline.Command
@@ -45,7 +46,11 @@
return
}
- if (keyguardBlueprintInteractor.transitionToBlueprint(arg)) {
+ if (
+ arg.isDigitsOnly() && keyguardBlueprintInteractor.transitionToBlueprint(arg.toInt())
+ ) {
+ pw.println("Transition succeeded!")
+ } else if (keyguardBlueprintInteractor.transitionToBlueprint(arg)) {
pw.println("Transition succeeded!")
} else {
pw.println("Invalid argument! To see available blueprint ids, run:")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index 79a97fb..6534dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -61,6 +61,6 @@
)
companion object {
- const val SHORTCUTS_BESIDE_UDFPS = "shortcutsBesideUdfps"
+ const val SHORTCUTS_BESIDE_UDFPS = "shortcuts-besides-udfps"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
index 79b7157..5aba229 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
@@ -18,8 +18,6 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.res.Resources
-import android.view.View
-import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -27,13 +25,10 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import androidx.constraintlayout.widget.ConstraintSet.TOP
-import androidx.core.content.res.ResourcesCompat
import com.android.systemui.R
-import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -53,10 +48,7 @@
private val falsingManager: FalsingManager,
private val indicationController: KeyguardIndicationController,
private val vibratorHelper: VibratorHelper,
-) : KeyguardSection() {
- private var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
- private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
-
+) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
addLeftShortcut(constraintLayout)
@@ -109,67 +101,4 @@
connect(R.id.end_button, BOTTOM, R.id.lock_icon_view, BOTTOM)
}
}
-
- override fun removeViews(constraintLayout: ConstraintLayout) {
- leftShortcutHandle?.destroy()
- rightShortcutHandle?.destroy()
- constraintLayout.removeView(R.id.start_button)
- constraintLayout.removeView(R.id.end_button)
- }
-
- private fun addLeftShortcut(constraintLayout: ConstraintLayout) {
- val padding =
- constraintLayout.resources.getDimensionPixelSize(
- R.dimen.keyguard_affordance_fixed_padding
- )
- val view =
- LaunchableImageView(constraintLayout.context, null).apply {
- id = R.id.start_button
- scaleType = ImageView.ScaleType.FIT_CENTER
- background =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_bg,
- context.theme
- )
- foreground =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_selected_border,
- context.theme
- )
- visibility = View.INVISIBLE
- setPadding(padding, padding, padding, padding)
- }
- constraintLayout.addView(view)
- }
-
- private fun addRightShortcut(constraintLayout: ConstraintLayout) {
- if (constraintLayout.findViewById<View>(R.id.end_button) != null) return
-
- val padding =
- constraintLayout.resources.getDimensionPixelSize(
- R.dimen.keyguard_affordance_fixed_padding
- )
- val view =
- LaunchableImageView(constraintLayout.context, null).apply {
- id = R.id.end_button
- scaleType = ImageView.ScaleType.FIT_CENTER
- background =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_bg,
- context.theme
- )
- foreground =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_selected_border,
- context.theme
- )
- visibility = View.INVISIBLE
- setPadding(padding, padding, padding, padding)
- }
- constraintLayout.addView(view)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutSection.kt
new file mode 100644
index 0000000..d046a19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutSection.kt
@@ -0,0 +1,99 @@
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.view.View
+import android.widget.ImageView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.res.ResourcesCompat
+import com.android.systemui.R
+import com.android.systemui.animation.view.LaunchableImageView
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
+
+abstract class BaseShortcutSection : KeyguardSection() {
+ protected var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
+ protected var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
+
+ override fun removeViews(constraintLayout: ConstraintLayout) {
+ leftShortcutHandle?.destroy()
+ rightShortcutHandle?.destroy()
+ constraintLayout.removeView(R.id.start_button)
+ constraintLayout.removeView(R.id.end_button)
+ }
+
+ protected fun addLeftShortcut(constraintLayout: ConstraintLayout) {
+ val padding =
+ constraintLayout.resources.getDimensionPixelSize(
+ R.dimen.keyguard_affordance_fixed_padding
+ )
+ val view =
+ LaunchableImageView(constraintLayout.context, null).apply {
+ id = R.id.start_button
+ scaleType = ImageView.ScaleType.FIT_CENTER
+ background =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_bg,
+ context.theme
+ )
+ foreground =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_selected_border,
+ context.theme
+ )
+ visibility = View.INVISIBLE
+ setPadding(padding, padding, padding, padding)
+ }
+ constraintLayout.addView(view)
+ }
+
+ protected fun addRightShortcut(constraintLayout: ConstraintLayout) {
+ if (constraintLayout.findViewById<View>(R.id.end_button) != null) return
+
+ val padding =
+ constraintLayout.resources.getDimensionPixelSize(
+ R.dimen.keyguard_affordance_fixed_padding
+ )
+ val view =
+ LaunchableImageView(constraintLayout.context, null).apply {
+ id = R.id.end_button
+ scaleType = ImageView.ScaleType.FIT_CENTER
+ background =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_bg,
+ context.theme
+ )
+ foreground =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_selected_border,
+ context.theme
+ )
+ visibility = View.INVISIBLE
+ setPadding(padding, padding, padding, padding)
+ }
+ constraintLayout.addView(view)
+ }
+ /**
+ * Defines equality as same class.
+ *
+ * This is to enable set operations to be done as an optimization to blueprint transitions.
+ */
+ override fun equals(other: Any?): Boolean {
+ return other is BaseShortcutSection
+ }
+
+ /**
+ * Defines hashcode as class.
+ *
+ * This is to enable set operations to be done as an optimization to blueprint transitions.
+ */
+ override fun hashCode(): Int {
+ return KEY.hashCode()
+ }
+
+ companion object {
+ private const val KEY = "shortcuts"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index a2db1df..13ef985 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -18,21 +18,16 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.res.Resources
-import android.view.View
-import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.LEFT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
-import androidx.core.content.res.ResourcesCompat
import com.android.systemui.R
-import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -52,10 +47,7 @@
private val falsingManager: FalsingManager,
private val indicationController: KeyguardIndicationController,
private val vibratorHelper: VibratorHelper,
-) : KeyguardSection() {
- private var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
- private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
-
+) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
addLeftShortcut(constraintLayout)
@@ -108,67 +100,4 @@
connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
}
}
-
- override fun removeViews(constraintLayout: ConstraintLayout) {
- leftShortcutHandle?.destroy()
- rightShortcutHandle?.destroy()
- constraintLayout.removeView(R.id.start_button)
- constraintLayout.removeView(R.id.end_button)
- }
-
- private fun addLeftShortcut(constraintLayout: ConstraintLayout) {
- val padding =
- constraintLayout.resources.getDimensionPixelSize(
- R.dimen.keyguard_affordance_fixed_padding
- )
- val view =
- LaunchableImageView(constraintLayout.context, null).apply {
- id = R.id.start_button
- scaleType = ImageView.ScaleType.FIT_CENTER
- background =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_bg,
- context.theme
- )
- foreground =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_selected_border,
- context.theme
- )
- visibility = View.INVISIBLE
- setPadding(padding, padding, padding, padding)
- }
- constraintLayout.addView(view)
- }
-
- private fun addRightShortcut(constraintLayout: ConstraintLayout) {
- if (constraintLayout.findViewById<View>(R.id.end_button) != null) return
-
- val padding =
- constraintLayout.resources.getDimensionPixelSize(
- R.dimen.keyguard_affordance_fixed_padding
- )
- val view =
- LaunchableImageView(constraintLayout.context, null).apply {
- id = R.id.end_button
- scaleType = ImageView.ScaleType.FIT_CENTER
- background =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_bg,
- context.theme
- )
- foreground =
- ResourcesCompat.getDrawable(
- context.resources,
- R.drawable.keyguard_bottom_affordance_selected_border,
- context.theme
- )
- visibility = View.INVISIBLE
- setPadding(padding, padding, padding, padding)
- }
- constraintLayout.addView(view)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index cca96b7..0783181 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -19,27 +19,36 @@
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.statusbar.SysuiStatusBarStateController
+import dagger.Lazy
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
/**
* Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to
* consume.
*/
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class PrimaryBouncerToGoneTransitionViewModel
@Inject
constructor(
- private val interactor: KeyguardTransitionInteractor,
+ interactor: KeyguardTransitionInteractor,
private val statusBarStateController: SysuiStatusBarStateController,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
+ featureFlags: FeatureFlagsClassic,
) {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
@@ -52,11 +61,18 @@
/** Bouncer container alpha */
val bouncerAlpha: Flow<Float> =
- transitionAnimation.createFlow(
+ if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ keyguardDismissActionInteractor
+ .get()
+ .willAnimateDismissActionOnLockscreen
+ .flatMapLatest { createBouncerAlphaFlow { it } }
+ } else {
+ createBouncerAlphaFlow(primaryBouncerInteractor::willRunDismissFromKeyguard)
+ }
+ private fun createBouncerAlphaFlow(willRunAnimationOnKeyguard: () -> Boolean): Flow<Float> {
+ return transitionAnimation.createFlow(
duration = 200.milliseconds,
- onStart = {
- willRunDismissFromKeyguard = primaryBouncerInteractor.willRunDismissFromKeyguard()
- },
+ onStart = { willRunDismissFromKeyguard = willRunAnimationOnKeyguard() },
onStep = {
if (willRunDismissFromKeyguard) {
0f
@@ -65,14 +81,24 @@
}
},
)
+ }
/** Lockscreen alpha */
val lockscreenAlpha: Flow<Float> =
- transitionAnimation.createFlow(
+ if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ keyguardDismissActionInteractor
+ .get()
+ .willAnimateDismissActionOnLockscreen
+ .flatMapLatest { createLockscreenAlpha { it } }
+ } else {
+ createLockscreenAlpha(primaryBouncerInteractor::willRunDismissFromKeyguard)
+ }
+ private fun createLockscreenAlpha(willRunAnimationOnKeyguard: () -> Boolean): Flow<Float> {
+ return transitionAnimation.createFlow(
duration = 50.milliseconds,
onStart = {
leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
- willRunDismissFromKeyguard = primaryBouncerInteractor.willRunDismissFromKeyguard()
+ willRunDismissFromKeyguard = willRunAnimationOnKeyguard()
},
onStep = {
if (willRunDismissFromKeyguard || leaveShadeOpen) {
@@ -82,17 +108,26 @@
}
},
)
+ }
/** Scrim alpha values */
val scrimAlpha: Flow<ScrimAlpha> =
- transitionAnimation
+ if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ keyguardDismissActionInteractor
+ .get()
+ .willAnimateDismissActionOnLockscreen
+ .flatMapLatest { createScrimAlphaFlow { it } }
+ } else {
+ createScrimAlphaFlow(primaryBouncerInteractor::willRunDismissFromKeyguard)
+ }
+ private fun createScrimAlphaFlow(willRunAnimationOnKeyguard: () -> Boolean): Flow<ScrimAlpha> {
+ return transitionAnimation
.createFlow(
duration = TO_GONE_DURATION,
interpolator = EMPHASIZED_ACCELERATE,
onStart = {
leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
- willRunDismissFromKeyguard =
- primaryBouncerInteractor.willRunDismissFromKeyguard()
+ willRunDismissFromKeyguard = willRunAnimationOnKeyguard()
},
onStep = { 1f - it },
)
@@ -108,4 +143,5 @@
ScrimAlpha(behindAlpha = it)
}
}
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 053c9b5..60fd104 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -29,7 +29,10 @@
import android.os.IBinder
import android.os.ResultReceiver
import android.os.UserHandle
+import android.util.Log
+import android.view.View
import android.view.ViewGroup
+import android.view.accessibility.AccessibilityEvent
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
@@ -40,6 +43,9 @@
import com.android.internal.app.ResolverListController
import com.android.internal.app.chooser.NotSelectableTargetInfo
import com.android.internal.app.chooser.TargetInfo
+import com.android.internal.widget.RecyclerView
+import com.android.internal.widget.RecyclerViewAccessibilityDelegate
+import com.android.internal.widget.ResolverDrawerLayout
import com.android.systemui.R
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
@@ -105,6 +111,10 @@
super.onCreate(bundle)
controller.init()
+ // we override AppList's AccessibilityDelegate set in ResolverActivity.onCreate because in
+ // our case this delegate must extend RecyclerViewAccessibilityDelegate, otherwise
+ // RecyclerView scrolling is broken
+ setAppListAccessibilityDelegate()
}
override fun onStart() {
@@ -277,6 +287,8 @@
recentsViewController.createView(parent)
companion object {
+ const val TAG = "MediaProjectionAppSelectorActivity"
+
/**
* When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra the activity will
* send the [CaptureRegion] to the result receiver instead of returning media projection
@@ -313,4 +325,42 @@
putExtra(EXTRA_SELECTED_PROFILE, selectedProfile)
}
}
+
+ private fun setAppListAccessibilityDelegate() {
+ val rdl = requireViewById<ResolverDrawerLayout>(com.android.internal.R.id.contentPanel)
+ for (i in 0 until mMultiProfilePagerAdapter.count) {
+ val list =
+ mMultiProfilePagerAdapter
+ .getItem(i)
+ .rootView
+ .findViewById<View>(com.android.internal.R.id.resolver_list)
+ if (list == null || list !is RecyclerView) {
+ Log.wtf(TAG, "MediaProjection only supports RecyclerView")
+ } else {
+ list.accessibilityDelegate = RecyclerViewExpandingAccessibilityDelegate(rdl, list)
+ }
+ }
+ }
+
+ /**
+ * An a11y delegate propagating all a11y events to [AppListAccessibilityDelegate] so that it can
+ * expand drawer when needed. It needs to extend [RecyclerViewAccessibilityDelegate] because
+ * that superclass handles RecyclerView scrolling while using a11y services.
+ */
+ private class RecyclerViewExpandingAccessibilityDelegate(
+ rdl: ResolverDrawerLayout,
+ view: RecyclerView
+ ) : RecyclerViewAccessibilityDelegate(view) {
+
+ private val delegate = AppListAccessibilityDelegate(rdl)
+
+ override fun onRequestSendAccessibilityEvent(
+ host: ViewGroup,
+ child: View,
+ event: AccessibilityEvent
+ ): Boolean {
+ super.onRequestSendAccessibilityEvent(host, child, event)
+ return delegate.onRequestSendAccessibilityEvent(host, child, event)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index 4be572f..d403788 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -29,6 +29,7 @@
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
+import android.app.StatusBarManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -72,6 +73,7 @@
private final FeatureFlags mFeatureFlags;
private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
+ private final StatusBarManager mStatusBarManager;
private String mPackageName;
private int mUid;
@@ -87,9 +89,11 @@
@Inject
public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
- Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) {
+ Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
+ StatusBarManager statusBarManager) {
mFeatureFlags = featureFlags;
mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
+ mStatusBarManager = statusBarManager;
}
@Override
@@ -311,6 +315,8 @@
// WM Shell running inside.
mUserSelectingTask = true;
startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser()));
+ // close shade if it's open
+ mStatusBarManager.collapsePanels();
}
} catch (RemoteException e) {
Log.e(TAG, "Error granting projection permission", e);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index bb0e9d1..7011ad9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -127,7 +127,7 @@
private final ActivityStarter mActivityStarter;
private final DialogLaunchAnimator mDialogLaunchAnimator;
private final CommonNotifCollection mNotifCollection;
- private final Object mMediaDevicesLock = new Object();
+ protected final Object mMediaDevicesLock = new Object();
@VisibleForTesting
final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
@@ -222,7 +222,7 @@
R.dimen.media_output_dialog_selectable_margin_end);
}
- void start(@NonNull Callback cb) {
+ protected void start(@NonNull Callback cb) {
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
mMediaItemList.clear();
@@ -256,15 +256,15 @@
return false;
}
- boolean isRefreshing() {
+ public boolean isRefreshing() {
return mIsRefreshing;
}
- void setRefreshing(boolean refreshing) {
+ public void setRefreshing(boolean refreshing) {
mIsRefreshing = refreshing;
}
- void stop() {
+ protected void stop() {
if (mMediaController != null) {
mMediaController.unregisterCallback(mCb);
}
@@ -551,7 +551,7 @@
}
}
- void refreshDataSetIfNeeded() {
+ public void refreshDataSetIfNeeded() {
if (mNeedRefresh) {
buildMediaItems(mCachedMediaDevices);
mCallback.onDeviceListChanged();
@@ -605,6 +605,15 @@
private void buildMediaItems(List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
+ List<MediaItem> updatedMediaItems = buildMediaItems(mMediaItemList, devices);
+ mMediaItemList.clear();
+ mMediaItemList.addAll(updatedMediaItems);
+ }
+ }
+
+ protected List<MediaItem> buildMediaItems(List<MediaItem> oldMediaItems,
+ List<MediaDevice> devices) {
+ synchronized (mMediaDevicesLock) {
if (!mLocalMediaManager.isPreferenceRouteListingExist()) {
attachRangeInfo(devices);
Collections.sort(devices, Comparator.naturalOrder());
@@ -616,22 +625,20 @@
final MediaDevice connectedMediaDevice =
needToHandleMutingExpectedDevice ? null
: getCurrentConnectedMediaDevice();
- if (mMediaItemList.isEmpty()) {
+ if (oldMediaItems.isEmpty()) {
if (connectedMediaDevice == null) {
if (DEBUG) {
Log.d(TAG, "No connected media device or muting expected device exist.");
}
- categorizeMediaItems(null, devices, needToHandleMutingExpectedDevice);
- return;
+ return categorizeMediaItems(null, devices, needToHandleMutingExpectedDevice);
}
// selected device exist
- categorizeMediaItems(connectedMediaDevice, devices, false);
- return;
+ return categorizeMediaItems(connectedMediaDevice, devices, false);
}
// To keep the same list order
final List<MediaDevice> targetMediaDevices = new ArrayList<>();
final Map<Integer, MediaItem> dividerItems = new HashMap<>();
- for (MediaItem originalMediaItem : mMediaItemList) {
+ for (MediaItem originalMediaItem : oldMediaItems) {
for (MediaDevice newDevice : devices) {
if (originalMediaItem.getMediaDevice().isPresent()
&& TextUtils.equals(originalMediaItem.getMediaDevice().get().getId(),
@@ -642,7 +649,7 @@
}
if (originalMediaItem.getMediaItemType()
== MediaItem.MediaItemType.TYPE_GROUP_DIVIDER) {
- dividerItems.put(mMediaItemList.indexOf(originalMediaItem), originalMediaItem);
+ dividerItems.put(oldMediaItems.indexOf(originalMediaItem), originalMediaItem);
}
}
if (targetMediaDevices.size() != devices.size()) {
@@ -651,16 +658,18 @@
}
List<MediaItem> finalMediaItems = targetMediaDevices.stream().map(
MediaItem::new).collect(Collectors.toList());
- dividerItems.forEach((key, item) -> {
- finalMediaItems.add(key, item);
- });
+ dividerItems.forEach(finalMediaItems::add);
attachConnectNewDeviceItemIfNeeded(finalMediaItems);
- mMediaItemList.clear();
- mMediaItemList.addAll(finalMediaItems);
+ return finalMediaItems;
}
}
- private void categorizeMediaItems(MediaDevice connectedMediaDevice, List<MediaDevice> devices,
+ /**
+ * Initial categorization of current devices, will not be called for updates to the devices
+ * list.
+ */
+ private List<MediaItem> categorizeMediaItems(MediaDevice connectedMediaDevice,
+ List<MediaDevice> devices,
boolean needToHandleMutingExpectedDevice) {
synchronized (mMediaDevicesLock) {
List<MediaItem> finalMediaItems = new ArrayList<>();
@@ -691,8 +700,7 @@
}
}
attachConnectNewDeviceItemIfNeeded(finalMediaItems);
- mMediaItemList.clear();
- mMediaItemList.addAll(finalMediaItems);
+ return finalMediaItems;
}
}
@@ -765,7 +773,7 @@
mGroupMediaDevices.clear();
}
- void connectDevice(MediaDevice device) {
+ protected void connectDevice(MediaDevice device) {
mMetricLogger.updateOutputEndPoints(getCurrentConnectedMediaDevice(), device);
ThreadUtils.postOnBackgroundThread(() -> {
@@ -777,7 +785,7 @@
return mMediaItemList;
}
- MediaDevice getCurrentConnectedMediaDevice() {
+ public MediaDevice getCurrentConnectedMediaDevice() {
return mLocalMediaManager.getCurrentConnectedDevice();
}
@@ -794,7 +802,7 @@
return mLocalMediaManager.getSelectableMediaDevice();
}
- List<MediaDevice> getSelectedMediaDevice() {
+ public List<MediaDevice> getSelectedMediaDevice() {
return mLocalMediaManager.getSelectedMediaDevice();
}
@@ -859,7 +867,7 @@
UserHandle.of(UserHandle.myUserId()));
}
- boolean isAnyDeviceTransferring() {
+ public boolean isAnyDeviceTransferring() {
synchronized (mMediaDevicesLock) {
for (MediaItem mediaItem : mMediaItemList) {
if (mediaItem.getMediaDevice().isPresent()
@@ -976,7 +984,7 @@
broadcast.setBroadcastCode(broadcastCode.getBytes(StandardCharsets.UTF_8));
}
- void setTemporaryAllowListExceptionIfNeeded(MediaDevice targetDevice) {
+ protected void setTemporaryAllowListExceptionIfNeeded(MediaDevice targetDevice) {
if (mPowerExemptionManager == null || mPackageName == null) {
Log.w(TAG, "powerExemptionManager or package name is null");
return;
@@ -1221,7 +1229,7 @@
}
};
- interface Callback {
+ public interface Callback {
/**
* Override to handle the media content updating.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index af65937..2b38edb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -38,7 +38,7 @@
/**
* Factory to create [MediaOutputDialog] objects.
*/
-class MediaOutputDialogFactory @Inject constructor(
+open class MediaOutputDialogFactory @Inject constructor(
private val context: Context,
private val mediaSessionManager: MediaSessionManager,
private val lbm: LocalBluetoothManager?,
@@ -60,7 +60,7 @@
}
/** Creates a [MediaOutputDialog] for the given package. */
- fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+ open fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
// Dismiss the previous dialog, if any.
mediaOutputDialog?.dismiss()
@@ -89,7 +89,7 @@
}
/** dismiss [MediaOutputDialog] if exist. */
- fun dismiss() {
+ open fun dismiss() {
mediaOutputDialog?.dismiss()
mediaOutputDialog = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 555269d..62b22c5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -133,7 +133,6 @@
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.shared.recents.utilities.Utilities;
@@ -156,6 +155,7 @@
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.ViewController;
import com.android.wm.shell.back.BackAnimation;
@@ -200,7 +200,7 @@
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final SysUiState mSysUiFlagsContainer;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
- private final ShadeController mShadeController;
+ private final KeyguardStateController mKeyguardStateController;
private final ShadeViewController mShadeViewController;
private final NotificationRemoteInputManager mNotificationRemoteInputManager;
private final OverviewProxyService mOverviewProxyService;
@@ -262,7 +262,7 @@
@VisibleForTesting
public int mDisplayId;
private boolean mIsOnDefaultDisplay;
- public boolean mHomeBlockedThisTouch;
+ private boolean mHomeBlockedThisTouch;
/**
* When user is QuickSwitching between apps of different orientations, we'll draw a fake
@@ -531,7 +531,6 @@
@Inject
NavigationBar(
NavigationBarView navigationBarView,
- ShadeController shadeController,
NavigationBarFrame navigationBarFrame,
@Nullable Bundle savedState,
@DisplayId Context context,
@@ -550,6 +549,7 @@
Optional<Pip> pipOptional,
Optional<Recents> recentsOptional,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
+ KeyguardStateController keyguardStateController,
ShadeViewController shadeViewController,
NotificationRemoteInputManager notificationRemoteInputManager,
NotificationShadeDepthController notificationShadeDepthController,
@@ -585,7 +585,7 @@
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mSysUiFlagsContainer = sysUiFlagsContainer;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
- mShadeController = shadeController;
+ mKeyguardStateController = keyguardStateController;
mShadeViewController = shadeViewController;
mNotificationRemoteInputManager = notificationRemoteInputManager;
mOverviewProxyService = overviewProxyService;
@@ -1326,8 +1326,7 @@
mHomeBlockedThisTouch = false;
if (mTelecomManagerOptional.isPresent()
&& mTelecomManagerOptional.get().isRinging()) {
- if (centralSurfacesOptional.map(CentralSurfaces::isKeyguardShowing)
- .orElse(false)) {
+ if (mKeyguardStateController.isShowing()) {
Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
"No heads up");
mHomeBlockedThisTouch = true;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 30218a6..26912f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -226,7 +226,7 @@
listenToMetadata(device);
} else {
stopListeningToStaleDeviceMetadata();
- batteryLevel = device.getBatteryLevel();
+ batteryLevel = device.getMinBatteryLevelWithMemberDevices();
}
if (batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index 4b22edc..21c5ae8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.recents;
-import android.annotation.Nullable;
import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;
@@ -25,11 +24,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.shared.recents.IOverviewProxy;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-
-import dagger.Lazy;
-
-import java.util.Optional;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import javax.inject.Inject;
@@ -40,20 +35,20 @@
public class OverviewProxyRecentsImpl implements RecentsImplementation {
private final static String TAG = "OverviewProxyRecentsImpl";
- @Nullable
- private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
-
private Handler mHandler;
private final OverviewProxyService mOverviewProxyService;
private final ActivityStarter mActivityStarter;
+ private final KeyguardStateController mKeyguardStateController;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
- public OverviewProxyRecentsImpl(Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
- OverviewProxyService overviewProxyService, ActivityStarter activityStarter) {
- mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+ public OverviewProxyRecentsImpl(
+ OverviewProxyService overviewProxyService,
+ ActivityStarter activityStarter,
+ KeyguardStateController keyguardStateController) {
mOverviewProxyService = overviewProxyService;
mActivityStarter = activityStarter;
+ mKeyguardStateController = keyguardStateController;
}
@Override
@@ -101,9 +96,7 @@
}
};
// Preload only if device for current user is unlocked
- final Optional<CentralSurfaces> centralSurfacesOptional =
- mCentralSurfacesOptionalLazy.get();
- if (centralSurfacesOptional.map(CentralSurfaces::isKeyguardShowing).orElse(false)) {
+ if (mKeyguardStateController.isShowing()) {
mActivityStarter.executeRunnableDismissingKeyguard(
() -> mHandler.post(toggleRecents), null, true /* dismissShade */,
false /* afterKeyguardGone */,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 98f2fee..5154067 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -16,10 +16,8 @@
package com.android.systemui.screenshot
-import android.graphics.Insets
import android.util.Log
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.internal.util.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
@@ -49,64 +47,6 @@
/** For the Java Async version, to invoke the callback. */
@Application private val mainScope: CoroutineScope
) : ScreenshotRequestProcessor {
- /**
- * Inspects the incoming request, returning a potentially modified request depending on policy.
- *
- * @param request the request to process
- */
- // TODO: Delete once SCREENSHOT_METADATA flag is launched
- suspend fun process(request: ScreenshotRequest): ScreenshotRequest {
- var result = request
-
- // Apply work profile screenshots policy:
- //
- // If the focused app belongs to a work profile, transforms a full screen
- // (or partial) screenshot request to a task snapshot (provided image) screenshot.
-
- // Whenever displayContentInfo is fetched, the topComponent is also populated
- // regardless of the managed profile status.
-
- if (request.type != TAKE_SCREENSHOT_PROVIDED_IMAGE) {
- val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
- Log.d(TAG, "findPrimaryContent: $info")
-
- result = if (policy.isManagedProfile(info.user.identifier)) {
- val image = capture.captureTask(info.taskId)
- ?: error("Task snapshot returned a null Bitmap!")
-
- // Provide the task snapshot as the screenshot
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source)
- .setTopComponent(info.component)
- .setTaskId(info.taskId)
- .setUserId(info.user.identifier)
- .setBitmap(image)
- .setBoundsOnScreen(info.bounds)
- .setInsets(Insets.NONE)
- .build()
- } else {
- // Create a new request of the same type which includes the top component
- ScreenshotRequest.Builder(request.type, request.source)
- .setTopComponent(info.component).build()
- }
- }
-
- return result
- }
-
- /**
- * Note: This is for compatibility with existing Java. Prefer the suspending function when
- * calling from a Coroutine context.
- *
- * @param request the request to process
- * @param callback the callback to provide the processed request, invoked from the main thread
- */
- // TODO: Delete once SCREENSHOT_METADATA flag is launched
- fun processAsync(request: ScreenshotRequest, callback: Consumer<ScreenshotRequest>) {
- mainScope.launch {
- val result = process(request)
- callback.accept(result)
- }
- }
override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
var result = screenshot
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 6c886fc..c5bc2fb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -4,6 +4,7 @@
import android.os.Trace
import android.util.Log
import android.view.Display
+import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.internal.logging.UiEventLogger
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
@@ -55,7 +56,7 @@
onSaved: (Uri) -> Unit,
requestCallback: RequestCallback
) {
- val displayIds = getDisplaysToScreenshot()
+ val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
screenshotRequest.oneForEachDisplay(displayIds).forEach { screenshotData: ScreenshotData ->
dispatchToController(
@@ -93,8 +94,13 @@
.handleScreenshot(screenshotData, onSaved, callback)
}
- private fun getDisplaysToScreenshot(): List<Int> {
- return displays.value.filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId }
+ private fun getDisplaysToScreenshot(requestType: Int): List<Int> {
+ return if (requestType == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ // If this is a provided image, let's show the UI on the default display only.
+ listOf(Display.DEFAULT_DISPLAY)
+ } else {
+ displays.value.filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId }
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 4a76dd0..2dbcbc9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -40,6 +40,7 @@
import android.view.IWindowSession;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
@@ -409,9 +410,9 @@
private void applyForceShowNavigationFlag(NotificationShadeWindowState state) {
if (state.panelExpanded || state.bouncerShowing
|| ENABLE_REMOTE_INPUT && state.remoteInputActive) {
- mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+ mLpChanged.forciblyShownTypes |= WindowInsets.Type.navigationBars();
} else {
- mLpChanged.privateFlags &= ~LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+ mLpChanged.forciblyShownTypes &= ~WindowInsets.Type.navigationBars();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index 02f337a..447a15d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -33,6 +33,8 @@
* {@link com.android.systemui.keyguard.KeyguardViewMediator} and others.
*/
public interface ShadeController extends CoreStartable {
+ /** True if the shade UI is enabled on this particular Android variant and false otherwise. */
+ boolean isShadeEnabled();
/** Make our window larger and the shade expanded */
void instantExpandShade();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
index 5f95bca..82959ee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
@@ -23,6 +23,7 @@
/** Empty implementation of ShadeController for variants of Android without shades. */
@SysUISingleton
open class ShadeControllerEmptyImpl @Inject constructor() : ShadeController {
+ override fun isShadeEnabled() = false
override fun start() {}
override fun instantExpandShade() {}
override fun instantCollapseShade() {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 9a3e4e5..367449b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -114,6 +114,11 @@
}
@Override
+ public boolean isShadeEnabled() {
+ return true;
+ }
+
+ @Override
public void instantExpandShade() {
// Make our window larger and the panel expanded.
makeExpandedVisible(true /* force */);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 509921f..947259a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -42,7 +42,7 @@
/**
* The amount the lockscreen shade has dragged down by the user, [0-1]. 0 means fully collapsed,
- * 1 means fully expanded.
+ * 1 means fully expanded. Value resets to 0 when the user finishes dragging.
*/
val lockscreenShadeExpansion: StateFlow<Float>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 3b19411..95a072c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -20,6 +20,10 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
@@ -28,22 +32,29 @@
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Business logic for shade interactions. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class ShadeInteractor
@Inject
constructor(
@Application scope: CoroutineScope,
disableFlagsRepository: DisableFlagsRepository,
+ sceneContainerFlags: SceneContainerFlags,
+ sceneInteractorProvider: Provider<SceneInteractor>,
keyguardRepository: KeyguardRepository,
userSetupRepository: UserSetupRepository,
deviceProvisionedController: DeviceProvisionedController,
@@ -68,28 +79,45 @@
/** The amount [0-1] that the shade has been opened */
val shadeExpansion: Flow<Float> =
- combine(
- repository.lockscreenShadeExpansion,
- keyguardRepository.statusBarState,
- repository.legacyShadeExpansion,
- repository.qsExpansion,
- splitShadeEnabled
- ) { dragDownAmount, statusBarState, legacyShadeExpansion, qsExpansion, splitShadeEnabled ->
- when (statusBarState) {
- // legacyShadeExpansion is 1 instead of 0 when QS is expanded
- StatusBarState.SHADE ->
- if (!splitShadeEnabled && qsExpansion > 0f) 0f else legacyShadeExpansion
- StatusBarState.KEYGUARD -> dragDownAmount
- // This is required, as shadeExpansion gets reset to 0f even with the shade open
- StatusBarState.SHADE_LOCKED -> 1f
- }
+ if (sceneContainerFlags.isEnabled()) {
+ sceneBasedExpansion(sceneInteractorProvider.get(), SceneKey.Shade)
+ } else {
+ combine(
+ repository.lockscreenShadeExpansion,
+ keyguardRepository.statusBarState,
+ repository.legacyShadeExpansion,
+ repository.qsExpansion,
+ splitShadeEnabled
+ ) {
+ lockscreenShadeExpansion,
+ statusBarState,
+ legacyShadeExpansion,
+ qsExpansion,
+ splitShadeEnabled ->
+ when (statusBarState) {
+ // legacyShadeExpansion is 1 instead of 0 when QS is expanded
+ StatusBarState.SHADE ->
+ if (!splitShadeEnabled && qsExpansion > 0f) 0f else legacyShadeExpansion
+ StatusBarState.KEYGUARD -> lockscreenShadeExpansion
+ // dragDownAmount, which drives lockscreenShadeExpansion resets to 0f when
+ // the pointer is lifted and the lockscreen shade is fully expanded
+ StatusBarState.SHADE_LOCKED -> 1f
+ }
+ }
+ .distinctUntilChanged()
}
/**
* The amount [0-1] QS has been opened. Normal shade with notifications (QQS) visible will
* report 0f.
*/
- val qsExpansion: StateFlow<Float> = repository.qsExpansion
+ val qsExpansion: StateFlow<Float> =
+ if (sceneContainerFlags.isEnabled()) {
+ sceneBasedExpansion(sceneInteractorProvider.get(), SceneKey.QuickSettings)
+ .stateIn(scope, SharingStarted.Eagerly, 0f)
+ } else {
+ repository.qsExpansion
+ }
/** The amount [0-1] either QS or the shade has been opened */
val anyExpansion: StateFlow<Float> =
@@ -119,4 +147,26 @@
disableFlags.isQuickSettingsEnabled() &&
!isDozing
}
+
+ fun sceneBasedExpansion(sceneInteractor: SceneInteractor, sceneKey: SceneKey) =
+ sceneInteractor.transitionState
+ .flatMapLatest { state ->
+ when (state) {
+ is ObservableTransitionState.Idle ->
+ if (state.scene == sceneKey) {
+ flowOf(1f)
+ } else {
+ flowOf(0f)
+ }
+ is ObservableTransitionState.Transition ->
+ if (state.toScene == sceneKey) {
+ state.progress
+ } else if (state.fromScene == sceneKey) {
+ state.progress.map { progress -> 1 - progress }
+ } else {
+ flowOf(0f)
+ }
+ }
+ }
+ .distinctUntilChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
index 51eb9f7..c24e9dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
@@ -36,7 +36,7 @@
@SysUISingleton
public class NotifInflationErrorManager {
- Set<NotificationEntry> mErroredNotifs = new ArraySet<>();
+ Set<String> mErroredNotifs = new ArraySet<>();
List<NotifInflationErrorListener> mListeners = new ArrayList<>();
@Inject
@@ -48,7 +48,7 @@
* @param e the exception encountered while inflating
*/
public void setInflationError(NotificationEntry entry, Exception e) {
- mErroredNotifs.add(entry);
+ mErroredNotifs.add(entry.getKey());
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onNotifInflationError(entry, e);
}
@@ -58,8 +58,8 @@
* Notification inflated successfully and is no longer errored out.
*/
public void clearInflationError(NotificationEntry entry) {
- if (mErroredNotifs.contains(entry)) {
- mErroredNotifs.remove(entry);
+ if (mErroredNotifs.contains(entry.getKey())) {
+ mErroredNotifs.remove(entry.getKey());
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onNotifInflationErrorCleared(entry);
}
@@ -70,7 +70,7 @@
* Whether or not the notification encountered an exception while inflating.
*/
public boolean hasInflationError(@NonNull NotificationEntry entry) {
- return mErroredNotifs.contains(entry);
+ return mErroredNotifs.contains(entry.getKey());
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index d8f513c..93b5ff7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1659,8 +1659,9 @@
@VisibleForTesting
void onKeyguardTransitionChanged(TransitionStep transitionStep) {
- boolean isTransitionToAod = transitionStep.getFrom().equals(KeyguardState.GONE)
- && transitionStep.getTo().equals(KeyguardState.AOD);
+ boolean isTransitionToAod = transitionStep.getTo().equals(KeyguardState.AOD)
+ && (transitionStep.getFrom().equals(KeyguardState.GONE)
+ || transitionStep.getFrom().equals(KeyguardState.OCCLUDED));
if (mIsInTransitionToAod != isTransitionToAod) {
mIsInTransitionToAod = isTransitionToAod;
updateShowEmptyShadeView();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 6f4adeb..f750fed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -44,7 +44,13 @@
shadeInteractor: ShadeInteractor,
) {
private val statesForConstrainedNotifications =
- setOf(KeyguardState.LOCKSCREEN, KeyguardState.AOD, KeyguardState.DOZING)
+ setOf(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.AOD,
+ KeyguardState.DOZING,
+ KeyguardState.ALTERNATE_BOUNCER,
+ KeyguardState.PRIMARY_BOUNCER
+ )
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
interactor.configurationBasedDimensions
@@ -126,6 +132,7 @@
/**
* When on keyguard, there is limited space to display notifications so calculate how many could
* be shown. Otherwise, there is no limit since the vertical space will be scrollable.
+ *
* TODO: b/296606746 - Need to rerun logic when notifs change
*/
val maxNotifications: Flow<Int> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 0031b574..3b9afa1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -17,8 +17,6 @@
package com.android.systemui.statusbar.phone;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
-
-import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS;
import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME;
@@ -702,8 +700,7 @@
}
final boolean screenOff = !mUpdateMonitor.isDeviceInteractive();
- if (!mVibratorHelper.hasVibrator() && (screenOff || (mUpdateMonitor.isDreaming()
- && !mFeatureFlags.isEnabled(FP_LISTEN_OCCLUDING_APPS)))) {
+ if (!mVibratorHelper.hasVibrator() && screenOff) {
mLogger.d("wakeup device on authentication failure (device doesn't have a vibrator)");
startWakeAndUnlock(MODE_ONLY_WAKE);
} else if (biometricSourceType == BiometricSourceType.FINGERPRINT
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 67c0c94..b59e3e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -295,8 +295,6 @@
@VisibleForTesting
void updateScrimController();
- boolean isKeyguardShowing();
-
boolean shouldIgnoreTouch();
boolean isDeviceInteractive();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 98ba6d9..ba32fd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -81,7 +81,6 @@
override fun setTransitionToFullShadeProgress(transitionToFullShadeProgress: Float) {}
override fun setPrimaryBouncerHiddenFraction(expansion: Float) {}
override fun updateScrimController() {}
- override fun isKeyguardShowing() = false
override fun shouldIgnoreTouch() = false
override fun isDeviceInteractive() = false
override fun awakenDreams() {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index b45a688..30e139f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -24,11 +24,9 @@
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
-
import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
import static androidx.lifecycle.Lifecycle.State.RESUMED;
-
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
@@ -1433,7 +1431,7 @@
// - QS is expanded and we're swiping - swiping up now will hide QS, not dismiss the
// keyguard.
// - Shade is in QQS over keyguard - swiping up should take us back to keyguard
- if (!isKeyguardShowing()
+ if (!mKeyguardStateController.isShowing()
|| mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
|| mKeyguardStateController.isOccluded()
|| !mKeyguardStateController.canDismissLockScreen()
@@ -2883,7 +2881,8 @@
if (mDevicePolicyManager.getCameraDisabled(null,
mLockscreenUserManager.getCurrentUserId())) {
return false;
- } else if (isKeyguardShowing() && mStatusBarKeyguardViewManager.isSecure()) {
+ } else if (mKeyguardStateController.isShowing()
+ && mStatusBarKeyguardViewManager.isSecure()) {
// Check if the admin has disabled the camera specifically for the keyguard
return (mDevicePolicyManager.getKeyguardDisabledFeatures(null,
mLockscreenUserManager.getCurrentUserId())
@@ -2999,12 +2998,6 @@
Trace.endSection();
}
-
- @Override
- public boolean isKeyguardShowing() {
- return mKeyguardStateController.isShowing();
- }
-
@Override
public boolean shouldIgnoreTouch() {
return (mStatusBarStateController.isDozing()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 27b8406..3afbbfd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone;
import static android.view.WindowInsets.Type.navigationBars;
-
import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -67,9 +66,12 @@
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
+import com.android.systemui.keyguard.shared.model.DismissAction;
+import com.android.systemui.keyguard.shared.model.KeyguardDone;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -104,6 +106,7 @@
import javax.inject.Inject;
import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
/**
* Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
@@ -111,7 +114,7 @@
* which is in turn, reported to this class by the current
* {@link com.android.keyguard.KeyguardViewController}.
*/
-@SysUISingleton
+@ExperimentalCoroutinesApi @SysUISingleton
public class StatusBarKeyguardViewManager implements RemoteInputController.Callback,
StatusBarStateController.StateListener, ConfigurationController.ConfigurationListener,
ShadeExpansionListener, NavigationModeController.ModeChangedListener,
@@ -338,6 +341,7 @@
}
};
private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
+ private Lazy<KeyguardDismissActionInteractor> mKeyguardDismissActionInteractor;
@Inject
public StatusBarKeyguardViewManager(
@@ -367,7 +371,8 @@
ActivityStarter activityStarter,
KeyguardTransitionInteractor keyguardTransitionInteractor,
@Main CoroutineDispatcher mainDispatcher,
- Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor
+ Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor,
+ Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy
) {
mContext = context;
mViewMediatorCallback = callback;
@@ -400,6 +405,7 @@
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mMainDispatcher = mainDispatcher;
mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
+ mKeyguardDismissActionInteractor = keyguardDismissActionInteractorLazy;
}
KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -692,6 +698,45 @@
public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
boolean afterKeyguardGone, String message) {
+ if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (r == null) {
+ return;
+ }
+ Trace.beginSection("StatusBarKeyguardViewManager#interactorDismissWithAction");
+ if (afterKeyguardGone) {
+ mKeyguardDismissActionInteractor.get().setDismissAction(
+ new DismissAction.RunAfterKeyguardGone(
+ () -> {
+ r.onDismiss();
+ return null;
+ },
+ (cancelAction != null) ? cancelAction : () -> {},
+ message == null ? "" : message,
+ r.willRunAnimationOnKeyguard()
+ )
+ );
+ } else {
+ mKeyguardDismissActionInteractor.get().setDismissAction(
+ new DismissAction.RunImmediately(
+ () -> {
+ if (r.onDismiss()) {
+ return KeyguardDone.LATER;
+ } else {
+ return KeyguardDone.IMMEDIATE;
+ }
+ },
+ (cancelAction != null) ? cancelAction : () -> {},
+ message == null ? "" : message,
+ r.willRunAnimationOnKeyguard()
+ )
+ );
+ }
+
+ showBouncer(true);
+ Trace.endSection();
+ return;
+ }
+
if (mKeyguardStateController.isShowing()) {
try {
Trace.beginSection("StatusBarKeyguardViewManager#dismissWithAction");
@@ -705,9 +750,12 @@
return;
}
- mAfterKeyguardGoneAction = r;
- mKeyguardGoneCancelAction = cancelAction;
- mDismissActionWillAnimateOnKeyguard = r != null && r.willRunAnimationOnKeyguard();
+ if (!mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ mAfterKeyguardGoneAction = r;
+ mKeyguardGoneCancelAction = cancelAction;
+ mDismissActionWillAnimateOnKeyguard = r != null
+ && r.willRunAnimationOnKeyguard();
+ }
// If there is an alternate auth interceptor (like the UDFPS), show that one
// instead of the bouncer.
@@ -755,6 +803,12 @@
* Adds a {@param runnable} to be executed after Keyguard is gone.
*/
public void addAfterKeyguardGoneRunnable(Runnable runnable) {
+ if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (runnable != null) {
+ mKeyguardDismissActionInteractor.get().runAfterKeyguardGone(runnable);
+ }
+ return;
+ }
mAfterKeyguardGoneRunnables.add(runnable);
}
@@ -936,7 +990,11 @@
// We update the state (which will show the keyguard) only if an animation will run on
// the keyguard. If there is no animation, we wait before updating the state so that we
// go directly from bouncer to launcher/app.
- if (mDismissActionWillAnimateOnKeyguard) {
+ if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ if (mKeyguardDismissActionInteractor.get().runDismissAnimationOnKeyguard()) {
+ updateStates();
+ }
+ } else if (mDismissActionWillAnimateOnKeyguard) {
updateStates();
}
} else if (finishRunnable != null) {
@@ -1059,6 +1117,9 @@
}
private void executeAfterKeyguardGoneAction() {
+ if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ return;
+ }
if (mAfterKeyguardGoneAction != null) {
mAfterKeyguardGoneAction.onDismiss();
mAfterKeyguardGoneAction = null;
@@ -1351,10 +1412,10 @@
/**
* Notifies that the user has authenticated by other means than using the bouncer, for example,
- * fingerprint.
+ * fingerprint and the keyguard should immediately dismiss.
*/
public void notifyKeyguardAuthenticated(boolean strongAuth) {
- mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
+ mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedBiometrics(strongAuth);
if (mAlternateBouncerInteractor.isVisibleState()) {
hideAlternateBouncer(false);
@@ -1442,6 +1503,8 @@
pw.println(" isBouncerShowing(): " + isBouncerShowing());
pw.println(" bouncerIsOrWillBeShowing(): " + primaryBouncerIsOrWillBeShowing());
pw.println(" Registered KeyguardViewManagerCallbacks:");
+ pw.println(" refactorKeyguardDismissIntent enabled:"
+ + mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT));
for (KeyguardViewManagerCallback callback : mCallbacks) {
pw.println(" " + callback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index ecc996c..53662f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -53,8 +53,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
@@ -116,7 +116,7 @@
NotificationMediaManager notificationMediaManager,
NotificationGutsManager notificationGutsManager,
InitController initController,
- NotificationInterruptStateProvider notificationInterruptStateProvider,
+ VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
NotificationRemoteInputManager remoteInputManager,
NotificationRemoteInputManager.Callback remoteInputManagerCallback,
NotificationListContainer notificationListContainer) {
@@ -162,7 +162,7 @@
initController.addPostInitTask(() -> {
mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
- notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
+ visualInterruptionDecisionProvider.addLegacySuppressor(mInterruptSuppressor);
mLockscreenUserManager.setUpWithPresenter(this);
mGutsManager.setUpWithPresenter(
this, mNotifListContainer, mOnSettingsClickListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 9b0daca..945cc6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -38,8 +38,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
import com.android.systemui.statusbar.policy.bluetooth.ConnectionStatusModel;
@@ -64,7 +62,6 @@
CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
private static final String TAG = "BluetoothController";
- private final FeatureFlags mFeatureFlags;
private final DumpManager mDumpManager;
private final BluetoothLogger mLogger;
private final BluetoothRepository mBluetoothRepository;
@@ -89,7 +86,6 @@
@Inject
public BluetoothControllerImpl(
Context context,
- FeatureFlags featureFlags,
UserTracker userTracker,
DumpManager dumpManager,
BluetoothLogger logger,
@@ -97,7 +93,6 @@
@Main Looper mainLooper,
@Nullable LocalBluetoothManager localBluetoothManager,
@Nullable BluetoothAdapter bluetoothAdapter) {
- mFeatureFlags = featureFlags;
mDumpManager = dumpManager;
mLogger = logger;
mBluetoothRepository = bluetoothRepository;
@@ -252,37 +247,8 @@
}
private void updateConnected() {
- if (mFeatureFlags.isEnabled(Flags.NEW_BLUETOOTH_REPOSITORY)) {
- mBluetoothRepository.fetchConnectionStatusInBackground(
- getDevices(), this::onConnectionStatusFetched);
- } else {
- updateConnectedOld();
- }
- }
-
- /** Used only if {@link Flags.NEW_BLUETOOTH_REPOSITORY} is *not* enabled. */
- private void updateConnectedOld() {
- // Make sure our connection state is up to date.
- int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState();
- List<CachedBluetoothDevice> newList = new ArrayList<>();
- // If any of the devices are in a higher state than the adapter, move the adapter into
- // that state.
- for (CachedBluetoothDevice device : getDevices()) {
- int maxDeviceState = device.getMaxConnectionState();
- if (maxDeviceState > state) {
- state = maxDeviceState;
- }
- if (device.isConnected()) {
- newList.add(device);
- }
- }
-
- if (newList.isEmpty() && state == BluetoothAdapter.STATE_CONNECTED) {
- // If somehow we think we are connected, but have no connected devices, we aren't
- // connected.
- state = BluetoothAdapter.STATE_DISCONNECTED;
- }
- onConnectionStatusFetched(new ConnectionStatusModel(state, newList));
+ mBluetoothRepository.fetchConnectionStatusInBackground(
+ getDevices(), this::onConnectionStatusFetched);
}
private void onConnectionStatusFetched(ConnectionStatusModel status) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index 24987ab..f4cc0ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -21,7 +21,6 @@
import static android.view.WindowInsets.Type.tappableElement;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
@@ -44,6 +43,7 @@
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.WindowManager;
import com.android.internal.policy.SystemBarUtils;
@@ -361,9 +361,9 @@
|| state.mIsLaunchAnimationRunning
// Don't force-show the status bar if the user has already dismissed it.
|| state.mOngoingProcessRequiresStatusBarVisible) {
- mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
+ mLpChanged.forciblyShownTypes |= WindowInsets.Type.statusBars();
} else {
- mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
+ mLpChanged.forciblyShownTypes &= ~WindowInsets.Type.statusBars();
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 5da919b..7c1861e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -43,6 +43,7 @@
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
import com.android.systemui.biometrics.SideFpsController
import com.android.systemui.biometrics.SideFpsUiRequestSource
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.classifier.FalsingA11yDelegate
import com.android.systemui.classifier.FalsingCollector
@@ -72,6 +73,7 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.GlobalSettings
import com.google.common.truth.Truth
+import dagger.Lazy
import java.util.Optional
import junit.framework.Assert
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -154,6 +156,7 @@
private lateinit var sceneInteractor: SceneInteractor
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var authenticationInteractor: AuthenticationInteractor
+ @Mock private lateinit var primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor>
private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
private lateinit var underTest: KeyguardSecurityContainerController
@@ -193,6 +196,7 @@
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
+ featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
keyguardPasswordViewController =
KeyguardPasswordViewController(
@@ -257,7 +261,8 @@
userInteractor,
deviceProvisionedController,
faceAuthAccessibilityDelegate,
- keyguardTransitionInteractor
+ keyguardTransitionInteractor,
+ primaryBouncerInteractor,
) {
authenticationInteractor
}
@@ -381,6 +386,36 @@
}
@Test
+ fun showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode_simpin() {
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+ setupGetSecurityView(SecurityMode.SimPin)
+ verify(view)
+ .initMode(
+ eq(KeyguardSecurityContainer.MODE_ONE_HANDED),
+ eq(globalSettings),
+ eq(falsingManager),
+ eq(userSwitcherController),
+ any(),
+ eq(falsingA11yDelegate)
+ )
+ }
+
+ @Test
+ fun showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode_simpuk() {
+ testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+ setupGetSecurityView(SecurityMode.SimPuk)
+ verify(view)
+ .initMode(
+ eq(KeyguardSecurityContainer.MODE_ONE_HANDED),
+ eq(globalSettings),
+ eq(falsingManager),
+ eq(userSwitcherController),
+ any(),
+ eq(falsingA11yDelegate)
+ )
+ }
+
+ @Test
fun showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
setupGetSecurityView(SecurityMode.Password)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 0cd82f0..562d3a5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -29,7 +29,6 @@
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
-
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
@@ -39,16 +38,11 @@
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
-import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS;
-import static com.android.systemui.flags.Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
-
import static com.google.common.truth.Truth.assertThat;
-
import static junit.framework.Assert.assertEquals;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -145,7 +139,6 @@
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.FakeDisplayTracker;
@@ -299,7 +292,6 @@
private final Executor mBackgroundExecutor = Runnable::run;
private final Executor mMainExecutor = Runnable::run;
private TestableLooper mTestableLooper;
- private FakeFeatureFlags mFeatureFlags;
private Handler mHandler;
private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
private MockitoSession mMockitoSession;
@@ -354,9 +346,6 @@
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
- mFeatureFlags = new FakeFeatureFlags();
- mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
- mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, false);
when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
@@ -1593,8 +1582,6 @@
@Test
public void listenForFingerprint_whenOccludingAppPkgOnAllowlist()
throws RemoteException {
- mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
-
// GIVEN keyguard isn't visible (app occluding)
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
@@ -1616,8 +1603,6 @@
@Test
public void doNotListenForFingerprint_whenOccludingAppPkgNotOnAllowlist()
throws RemoteException {
- mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
-
// GIVEN keyguard isn't visible (app occluding)
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
@@ -2979,11 +2964,6 @@
}
@Test
- public void stopFaceAuthOnDisplayOffFlagNotEnabled_doNotRegisterForDisplayCallback() {
- assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(0);
- }
-
- @Test
public void onDisplayOn_nothingHappens() throws RemoteException {
// GIVEN
keyguardIsVisible();
@@ -3325,7 +3305,6 @@
clearInvocations(mFingerprintManager);
clearInvocations(mBiometricManager);
clearInvocations(mStatusBarStateController);
- mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, true);
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
setupBiometrics(mKeyguardUpdateMonitor);
assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(1);
@@ -3407,7 +3386,7 @@
mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
mFaceWakeUpTriggersConfig, mDevicePostureController,
- Optional.of(mInteractiveToAuthProvider), mFeatureFlags,
+ Optional.of(mInteractiveToAuthProvider),
mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
index 576f689..b478d5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
@@ -17,7 +17,6 @@
package com.android.systemui.accessibility;
import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
@@ -41,9 +40,7 @@
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-
-import dagger.Lazy;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
@@ -64,12 +61,12 @@
@Mock
private NotificationShadeWindowController mNotificationShadeController;
@Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
private ShadeController mShadeController;
@Mock
private ShadeViewController mShadeViewController;
@Mock
- private Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
- @Mock
private Optional<Recents> mRecentsOptional;
@Mock
private TelecomManager mTelecomManager;
@@ -84,9 +81,15 @@
MockitoAnnotations.initMocks(this);
mContext.addMockSystemService(TelecomManager.class, mTelecomManager);
mContext.addMockSystemService(InputManager.class, mInputManager);
- mSystemActions = new SystemActions(mContext, mUserTracker, mNotificationShadeController,
- mShadeController, () -> mShadeViewController, mCentralSurfacesOptionalLazy,
- mRecentsOptional, mDisplayTracker);
+ mSystemActions = new SystemActions(
+ mContext,
+ mUserTracker,
+ mNotificationShadeController,
+ mKeyguardStateController,
+ mShadeController,
+ () -> mShadeViewController,
+ mRecentsOptional,
+ mDisplayTracker);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 0fb0b03..39fe6fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -751,6 +751,245 @@
}
@Test
+ public void windowWidthIsNotMax_performA11yActionIncreaseWidth_windowWidthIncreased() {
+ final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ final int startingWidth = (int) (windowBounds.width() * 0.8);
+ final int startingHeight = (int) (windowBounds.height() * 0.8);
+ final float changeWindowSizeAmount = mContext.getResources().getFraction(
+ R.fraction.magnification_resize_window_size_amount,
+ /* base= */ 1,
+ /* pbase= */ 1);
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mWindowManager.getAttachedView();
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mirrorView.performAccessibilityAction(
+ R.id.accessibility_action_increase_window_width, null);
+ actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+ actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+ });
+
+ final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+ R.dimen.magnification_mirror_surface_margin);
+ // Window width includes the magnifier frame and the margin. Increasing the window size
+ // will be increasing the amount of the frame size only.
+ int newWindowWidth =
+ (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
+ + 2 * mirrorSurfaceMargin;
+ assertEquals(newWindowWidth, actualWindowWidth.get());
+ assertEquals(startingHeight, actualWindowHeight.get());
+ }
+
+ @Test
+ public void windowHeightIsNotMax_performA11yActionIncreaseHeight_windowHeightIncreased() {
+ final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ final int startingWidth = (int) (windowBounds.width() * 0.8);
+ final int startingHeight = (int) (windowBounds.height() * 0.8);
+ final float changeWindowSizeAmount = mContext.getResources().getFraction(
+ R.fraction.magnification_resize_window_size_amount,
+ /* base= */ 1,
+ /* pbase= */ 1);
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mWindowManager.getAttachedView();
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mirrorView.performAccessibilityAction(
+ R.id.accessibility_action_increase_window_height, null);
+ actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+ actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+ });
+
+ final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+ R.dimen.magnification_mirror_surface_margin);
+ // Window height includes the magnifier frame and the margin. Increasing the window size
+ // will be increasing the amount of the frame size only.
+ int newWindowHeight =
+ (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
+ + 2 * mirrorSurfaceMargin;
+ assertEquals(startingWidth, actualWindowWidth.get());
+ assertEquals(newWindowHeight, actualWindowHeight.get());
+ }
+
+ @Test
+ public void windowWidthIsMax_noIncreaseWindowWidthA11yAction() {
+ final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ final int startingWidth = windowBounds.width();
+ final int startingHeight = windowBounds.height();
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mWindowManager.getAttachedView();
+ final AccessibilityNodeInfo accessibilityNodeInfo =
+ mirrorView.createAccessibilityNodeInfo();
+ assertFalse(accessibilityNodeInfo.getActionList().contains(
+ new AccessibilityAction(R.id.accessibility_action_increase_window_width, null)));
+ }
+
+ @Test
+ public void windowHeightIsMax_noIncreaseWindowHeightA11yAction() {
+ final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ final int startingWidth = windowBounds.width();
+ final int startingHeight = windowBounds.height();
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mWindowManager.getAttachedView();
+ final AccessibilityNodeInfo accessibilityNodeInfo =
+ mirrorView.createAccessibilityNodeInfo();
+ assertFalse(accessibilityNodeInfo.getActionList().contains(
+ new AccessibilityAction(R.id.accessibility_action_increase_window_height, null)));
+ }
+
+ @Test
+ public void windowWidthIsNotMin_performA11yActionDecreaseWidth_windowWidthDecreased() {
+ int mMinWindowSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ final int startingSize = (int) (mMinWindowSize * 1.1);
+ final float changeWindowSizeAmount = mContext.getResources().getFraction(
+ R.fraction.magnification_resize_window_size_amount,
+ /* base= */ 1,
+ /* pbase= */ 1);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mWindowManager.getAttachedView();
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mirrorView.performAccessibilityAction(
+ R.id.accessibility_action_decrease_window_width, null);
+ actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+ actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+ });
+
+ final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+ R.dimen.magnification_mirror_surface_margin);
+ // Window width includes the magnifier frame and the margin. Decreasing the window size
+ // will be decreasing the amount of the frame size only.
+ int newWindowWidth =
+ (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
+ + 2 * mirrorSurfaceMargin;
+ assertEquals(newWindowWidth, actualWindowWidth.get());
+ assertEquals(startingSize, actualWindowHeight.get());
+ }
+
+ @Test
+ public void windowHeightIsNotMin_performA11yActionDecreaseHeight_windowHeightDecreased() {
+ int mMinWindowSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ final int startingSize = (int) (mMinWindowSize * 1.1);
+ final float changeWindowSizeAmount = mContext.getResources().getFraction(
+ R.fraction.magnification_resize_window_size_amount,
+ /* base= */ 1,
+ /* pbase= */ 1);
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mWindowManager.getAttachedView();
+ final AtomicInteger actualWindowHeight = new AtomicInteger();
+ final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mirrorView.performAccessibilityAction(
+ R.id.accessibility_action_decrease_window_height, null);
+ actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+ actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+ });
+
+ final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+ R.dimen.magnification_mirror_surface_margin);
+ // Window height includes the magnifier frame and the margin. Decreasing the window size
+ // will be decreasing the amount of the frame size only.
+ int newWindowHeight =
+ (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
+ + 2 * mirrorSurfaceMargin;
+ assertEquals(startingSize, actualWindowWidth.get());
+ assertEquals(newWindowHeight, actualWindowHeight.get());
+ }
+
+ @Test
+ public void windowWidthIsMin_noDecreaseWindowWidthA11yAction() {
+ int mMinWindowSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ final int startingSize = mMinWindowSize;
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mWindowManager.getAttachedView();
+ final AccessibilityNodeInfo accessibilityNodeInfo =
+ mirrorView.createAccessibilityNodeInfo();
+ assertFalse(accessibilityNodeInfo.getActionList().contains(
+ new AccessibilityAction(R.id.accessibility_action_decrease_window_width, null)));
+ }
+
+ @Test
+ public void windowHeightIsMin_noDecreaseWindowHeightA11yAcyion() {
+ int mMinWindowSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ final int startingSize = mMinWindowSize;
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+ mWindowMagnificationController.setEditMagnifierSizeMode(true);
+ });
+
+ final View mirrorView = mWindowManager.getAttachedView();
+ final AccessibilityNodeInfo accessibilityNodeInfo =
+ mirrorView.createAccessibilityNodeInfo();
+ assertFalse(accessibilityNodeInfo.getActionList().contains(
+ new AccessibilityAction(R.id.accessibility_action_decrease_window_height, null)));
+ }
+
+ @Test
public void enableWindowMagnification_hasA11yWindowTitle() {
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
@@ -1210,4 +1449,5 @@
when(mContext.getDisplay()).thenReturn(display);
return newRotation;
}
+
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 9584d88..d10b81c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -42,12 +42,14 @@
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakePromptRepository
import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -125,13 +127,8 @@
)
}
- private val displayStateInteractor = DisplayStateInteractorImpl(
- testScope.backgroundScope,
- mContext,
- fakeExecutor,
- rearDisplayStateRepository
- )
-
+ private lateinit var displayRepository: FakeDisplayRepository
+ private lateinit var displayStateInteractor: DisplayStateInteractor
private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
@@ -139,8 +136,18 @@
@Before
fun setup() {
+ displayRepository = FakeDisplayRepository()
featureFlags.set(Flags.BIOMETRIC_BP_STRONG, useNewBiometricPrompt)
featureFlags.set(Flags.ONE_WAY_HAPTICS_API_MIGRATION, false)
+
+ displayStateInteractor =
+ DisplayStateInteractorImpl(
+ testScope.backgroundScope,
+ mContext,
+ fakeExecutor,
+ rearDisplayStateRepository,
+ displayRepository,
+ )
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index 9751fad..2bb3785 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -28,6 +28,8 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
@@ -42,8 +44,6 @@
import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
import com.android.systemui.user.domain.interactor.UserInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -56,12 +56,15 @@
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
+ private val testDispatcher = utils.testDispatcher
private val disableFlagsRepository = FakeDisableFlagsRepository()
private val featureFlags = FakeFeatureFlags()
private val keyguardRepository = FakeKeyguardRepository()
private val shadeRepository = FakeShadeRepository()
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val sceneContainerFlags = FakeSceneContainerFlags()
+ private val sceneInteractor = utils.sceneInteractor()
private val userSetupRepository = FakeUserSetupRepository()
private val userRepository = FakeUserRepository()
private val configurationRepository = FakeConfigurationRepository()
@@ -126,6 +129,8 @@
ShadeInteractor(
testScope.backgroundScope,
disableFlagsRepository,
+ sceneContainerFlags,
+ { sceneInteractor },
keyguardRepository,
userSetupRepository,
deviceProvisionedController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 994db46..3ebc2d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -61,6 +61,7 @@
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -115,12 +116,13 @@
@Captor lateinit var overlayCaptor: ArgumentCaptor<View>
@Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
+ private lateinit var displayRepository: FakeDisplayRepository
+ private lateinit var rearDisplayStateRepository: FakeRearDisplayStateRepository
private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
private lateinit var displayStateInteractor: DisplayStateInteractor
private val executor = FakeExecutor(FakeSystemClock())
- private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
private val testScope = TestScope(StandardTestDispatcher())
private lateinit var overlayController: ISidefpsController
@@ -142,6 +144,8 @@
@Before
fun setup() {
+ displayRepository = FakeDisplayRepository()
+ rearDisplayStateRepository = FakeRearDisplayStateRepository()
keyguardBouncerRepository = FakeKeyguardBouncerRepository()
alternateBouncerInteractor =
AlternateBouncerInteractor(
@@ -157,7 +161,8 @@
testScope.backgroundScope,
context,
executor,
- rearDisplayStateRepository
+ rearDisplayStateRepository,
+ displayRepository,
)
context.addMockSystemService(DisplayManager::class.java, displayManager)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
index 7de78a6..469f65a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -21,6 +21,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
@@ -44,6 +45,7 @@
@Mock lateinit var udfpsBpView: UdfpsBpView
@Mock lateinit var statusBarStateController: StatusBarStateController
@Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock lateinit var systemUIDialogManager: SystemUIDialogManager
@Mock lateinit var dumpManager: DumpManager
@@ -55,12 +57,13 @@
UdfpsBpViewController(
udfpsBpView,
statusBarStateController,
- shadeExpansionStateManager,
+ primaryBouncerInteractor,
systemUIDialogManager,
dumpManager
)
}
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
@Test
fun testShouldNeverPauseAuth() {
assertFalse(udfpsBpViewController.shouldPauseAuth())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 0e0d0e3..6eb637b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -50,7 +50,6 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -97,7 +96,6 @@
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var accessibilityManager: AccessibilityManager
@Mock private lateinit var statusBarStateController: StatusBarStateController
- @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dialogManager: SystemUIDialogManager
@@ -145,13 +143,32 @@
block: () -> Unit
) {
controllerOverlay = UdfpsControllerOverlay(
- context, fingerprintManager, inflater, windowManager, accessibilityManager,
- statusBarStateController, shadeExpansionStateManager, statusBarKeyguardViewManager,
- keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
- configurationController, keyguardStateController, unlockedScreenOffAnimationController,
- udfpsDisplayMode, secureSettings, REQUEST_ID, reason,
- controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
- primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils,
+ context,
+ fingerprintManager,
+ inflater,
+ windowManager,
+ accessibilityManager,
+ statusBarStateController,
+ statusBarKeyguardViewManager,
+ keyguardUpdateMonitor,
+ dialogManager,
+ dumpManager,
+ transitionController,
+ configurationController,
+ keyguardStateController,
+ unlockedScreenOffAnimationController,
+ udfpsDisplayMode,
+ secureSettings,
+ REQUEST_ID,
+ reason,
+ controllerCallback,
+ onTouch,
+ activityLaunchAnimator,
+ featureFlags,
+ primaryBouncerInteractor,
+ alternateBouncerInteractor,
+ isDebuggable,
+ udfpsUtils,
udfpsKeyguardAccessibilityDelegate,
udfpsKeyguardViewModels,
)
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 e01b5af..755977f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -20,15 +20,12 @@
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
-
import static com.android.internal.util.FunctionalUtils.ThrowingConsumer;
import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
-
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -98,7 +95,6 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -314,19 +310,48 @@
(Provider<AlternateUdfpsTouchProvider>) () -> mAlternateTouchProvider)
: Optional.empty();
- mUdfpsController = new UdfpsController(mContext, new FakeExecution(), mLayoutInflater,
- mFingerprintManager, mWindowManager, mStatusBarStateController, mFgExecutor,
- new ShadeExpansionStateManager(), mStatusBarKeyguardViewManager, mDumpManager,
- mKeyguardUpdateMonitor, mFeatureFlags, mFalsingManager, mPowerManager,
- mAccessibilityManager, mLockscreenShadeTransitionController, mScreenLifecycle,
- mVibrator, mUdfpsHapticsSimulator, mUdfpsShell, mKeyguardStateController,
- mDisplayManager, mHandler, mConfigurationController, mSystemClock,
- mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
- mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
- mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
- mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils,
+ mUdfpsController = new UdfpsController(
+ mContext,
+ new FakeExecution(),
+ mLayoutInflater,
+ mFingerprintManager,
+ mWindowManager,
+ mStatusBarStateController,
+ mFgExecutor,
+ mStatusBarKeyguardViewManager,
+ mDumpManager,
+ mKeyguardUpdateMonitor,
+ mFeatureFlags,
+ mFalsingManager,
+ mPowerManager,
+ mAccessibilityManager,
+ mLockscreenShadeTransitionController,
+ mScreenLifecycle,
+ mVibrator,
+ mUdfpsHapticsSimulator,
+ mUdfpsShell,
+ mKeyguardStateController,
+ mDisplayManager,
+ mHandler,
+ mConfigurationController,
+ mSystemClock,
+ mUnlockedScreenOffAnimationController,
+ mSystemUIDialogManager,
+ mLatencyTracker,
+ mActivityLaunchAnimator,
+ alternateTouchProvider,
+ mBiometricExecutor,
+ mPrimaryBouncerInteractor,
+ mSinglePointerTouchProcessor,
+ mSessionTracker,
+ mAlternateBouncerInteractor,
+ mSecureSettings,
+ mInputManager,
+ mUdfpsUtils,
mock(KeyguardFaceAuthInteractor.class),
- mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels);
+ mUdfpsKeyguardAccessibilityDelegate,
+ mUdfpsKeyguardViewModels
+ );
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 032753a..3276e66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -18,7 +18,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -27,15 +26,14 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
-import com.android.systemui.shade.ShadeExpansionListener;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -51,8 +49,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.List;
-
public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase {
// Dependencies
protected @Mock UdfpsKeyguardViewLegacy mView;
@@ -83,9 +79,6 @@
private @Captor ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
protected StatusBarStateController.StateListener mStatusBarStateListener;
- private @Captor ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
- protected List<ShadeExpansionListener> mExpansionListeners;
-
private @Captor ArgumentCaptor<KeyguardStateController.Callback>
mKeyguardStateControllerCallbackCaptor;
protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
@@ -116,23 +109,6 @@
mStatusBarStateListener = mStateListenerCaptor.getValue();
}
- protected void captureStatusBarExpansionListeners() {
- verify(mShadeExpansionStateManager, times(2))
- .addExpansionListener(mExpansionListenerCaptor.capture());
- // first (index=0) is from super class, UdfpsAnimationViewController.
- // second (index=1) is from UdfpsKeyguardViewController
- mExpansionListeners = mExpansionListenerCaptor.getAllValues();
- }
-
- protected void updateStatusBarExpansion(float fraction, boolean expanded) {
- ShadeExpansionChangeEvent event =
- new ShadeExpansionChangeEvent(
- fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
- for (ShadeExpansionListener listener : mExpansionListeners) {
- listener.onPanelExpansionChanged(event);
- }
- }
-
protected void captureKeyguardStateControllerCallback() {
verify(mKeyguardStateController).addCallback(
mKeyguardStateControllerCallbackCaptor.capture());
@@ -155,7 +131,6 @@
UdfpsKeyguardViewControllerLegacy controller = new UdfpsKeyguardViewControllerLegacy(
mView,
mStatusBarStateController,
- mShadeExpansionStateManager,
mStatusBarKeyguardViewManager,
mKeyguardUpdateMonitor,
mDumpManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
index d24290f..8508f45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
@@ -21,9 +21,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -34,7 +32,6 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.RoboPilotTest;
-import com.android.systemui.shade.ShadeExpansionListener;
import com.android.systemui.statusbar.StatusBarState;
import org.junit.Test;
@@ -66,12 +63,6 @@
}
@Test
- public void testRegistersExpansionChangedListenerOnAttached() {
- mController.onViewAttached();
- captureStatusBarExpansionListeners();
- }
-
- @Test
public void testRegistersStatusBarStateListenersOnAttached() {
mController.onViewAttached();
captureStatusBarStateListeners();
@@ -98,14 +89,10 @@
public void testListenersUnregisteredOnDetached() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureStatusBarExpansionListeners();
captureKeyguardStateControllerCallback();
mController.onViewDetached();
verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
- for (ShadeExpansionListener listener : mExpansionListeners) {
- verify(mShadeExpansionStateManager).removeExpansionListener(listener);
- }
verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback);
}
@@ -134,23 +121,6 @@
}
@Test
- public void testFadeFromDialogSuggestedAlpha() {
- // GIVEN view is attached and status bar expansion is 1f
- mController.onViewAttached();
- captureStatusBarStateListeners();
- captureStatusBarExpansionListeners();
- updateStatusBarExpansion(1f, true);
- reset(mView);
-
- // WHEN dialog suggested alpha is .6f
- when(mView.getDialogSuggestedAlpha()).thenReturn(.6f);
- sendStatusBarStateChanged(StatusBarState.KEYGUARD);
-
- // THEN alpha is updated based on dialog suggested alpha
- verify(mView).setUnpausedAlpha((int) (.6f * 255));
- }
-
- @Test
public void testShouldNotPauseAuthOnKeyguard() {
mController.onViewAttached();
captureStatusBarStateListeners();
@@ -250,72 +220,6 @@
}
@Test
- public void testFadeInWithStatusBarExpansion() {
- // GIVEN view is attached
- mController.onViewAttached();
- captureStatusBarExpansionListeners();
- captureKeyguardStateControllerCallback();
- reset(mView);
-
- // WHEN status bar expansion is 0
- updateStatusBarExpansion(0, true);
-
- // THEN alpha is 0
- verify(mView).setUnpausedAlpha(0);
- }
-
- @Test
- public void testTransitionToFullShadeProgress() {
- // GIVEN view is attached and status bar expansion is 1f
- mController.onViewAttached();
- captureStatusBarExpansionListeners();
- updateStatusBarExpansion(1f, true);
- reset(mView);
- when(mView.getDialogSuggestedAlpha()).thenReturn(1f);
-
- // WHEN we're transitioning to the full shade
- float transitionProgress = .6f;
- mController.setTransitionToFullShadeProgress(transitionProgress);
-
- // THEN alpha is between 0 and 255
- verify(mView).setUnpausedAlpha((int) ((1f - transitionProgress) * 255));
- }
-
- @Test
- public void testUpdatePanelExpansion_pauseAuth() {
- // GIVEN view is attached + on the keyguard
- mController.onViewAttached();
- captureStatusBarStateListeners();
- captureStatusBarExpansionListeners();
- sendStatusBarStateChanged(StatusBarState.KEYGUARD);
- reset(mView);
-
- // WHEN panelViewExpansion changes to hide
- when(mView.getUnpausedAlpha()).thenReturn(0);
- updateStatusBarExpansion(0f, false);
-
- // THEN pause auth is updated to PAUSE
- verify(mView, atLeastOnce()).setPauseAuth(true);
- }
-
- @Test
- public void testUpdatePanelExpansion_unpauseAuth() {
- // GIVEN view is attached + on the keyguard + panel expansion is 0f
- mController.onViewAttached();
- captureStatusBarStateListeners();
- captureStatusBarExpansionListeners();
- sendStatusBarStateChanged(StatusBarState.KEYGUARD);
- reset(mView);
-
- // WHEN panelViewExpansion changes to expanded
- when(mView.getUnpausedAlpha()).thenReturn(255);
- updateStatusBarExpansion(1f, true);
-
- // THEN pause auth is updated to NOT pause
- verify(mView, atLeastOnce()).setPauseAuth(false);
- }
-
- @Test
// TODO(b/259264861): Tracking Bug
public void testUdfpsExpandedOverlayOn() {
// GIVEN view is attached and useExpandedOverlay is true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 8dfeb3b..1885f64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -39,8 +39,10 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -51,6 +53,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -62,15 +65,15 @@
@kotlinx.coroutines.ExperimentalCoroutinesApi
class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest :
UdfpsKeyguardViewLegacyControllerBaseTest() {
- lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
- @Mock private lateinit var bouncerLogger: TableLogBuffer
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
- private lateinit var testScope: TestScope
+ private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+
+ @Mock private lateinit var bouncerLogger: TableLogBuffer
@Before
override fun setUp() {
- testScope = TestScope()
-
allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
MockitoAnnotations.initMocks(this)
keyguardBouncerRepository =
@@ -82,7 +85,7 @@
super.setUp()
}
- override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewControllerLegacy? {
+ override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewControllerLegacy {
mPrimaryBouncerInteractor =
PrimaryBouncerInteractor(
keyguardBouncerRepository,
@@ -115,6 +118,70 @@
}
@Test
+ fun bouncerExpansionChange_fadeIn() =
+ testScope.runTest {
+ // GIVEN view is attached
+ mController.onViewAttached()
+ captureKeyguardStateControllerCallback()
+ Mockito.reset(mView)
+
+ // WHEN status bar expansion is 0
+ val job = mController.listenForBouncerExpansion(this)
+ keyguardBouncerRepository.setPrimaryShow(true)
+ keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+ runCurrent()
+
+ // THEN alpha is 0
+ verify(mView).unpausedAlpha = 0
+
+ job.cancel()
+ }
+
+ @Test
+ fun bouncerExpansionChange_pauseAuth() =
+ testScope.runTest {
+ // GIVEN view is attached + on the keyguard
+ mController.onViewAttached()
+ captureStatusBarStateListeners()
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+ Mockito.reset(mView)
+
+ // WHEN panelViewExpansion changes to hide
+ whenever(mView.unpausedAlpha).thenReturn(0)
+ val job = mController.listenForBouncerExpansion(this)
+ keyguardBouncerRepository.setPrimaryShow(true)
+ keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+ runCurrent()
+
+ // THEN pause auth is updated to PAUSE
+ verify(mView, Mockito.atLeastOnce()).setPauseAuth(true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun bouncerExpansionChange_unpauseAuth() =
+ testScope.runTest {
+ // GIVEN view is attached + on the keyguard + panel expansion is 0f
+ mController.onViewAttached()
+ captureStatusBarStateListeners()
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+ Mockito.reset(mView)
+
+ // WHEN panelViewExpansion changes to expanded
+ whenever(mView.unpausedAlpha).thenReturn(255)
+ val job = mController.listenForBouncerExpansion(this)
+ keyguardBouncerRepository.setPrimaryShow(true)
+ keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_HIDDEN)
+ runCurrent()
+
+ // THEN pause auth is updated to NOT pause
+ verify(mView, Mockito.atLeastOnce()).setPauseAuth(false)
+
+ job.cancel()
+ }
+
+ @Test
fun shadeLocked_showAlternateBouncer_unpauseAuth() =
testScope.runTest {
// GIVEN view is attached + on the SHADE_LOCKED (udfps view not showing)
@@ -154,4 +221,48 @@
job.cancel()
}
+
+ @Test
+ fun fadeFromDialogSuggestedAlpha() =
+ testScope.runTest {
+ // GIVEN view is attached and status bar expansion is 1f
+ mController.onViewAttached()
+ captureStatusBarStateListeners()
+ val job = mController.listenForBouncerExpansion(this)
+ keyguardBouncerRepository.setPrimaryShow(true)
+ keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_HIDDEN)
+ runCurrent()
+ Mockito.reset(mView)
+
+ // WHEN dialog suggested alpha is .6f
+ whenever(mView.dialogSuggestedAlpha).thenReturn(.6f)
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+
+ // THEN alpha is updated based on dialog suggested alpha
+ verify(mView).unpausedAlpha = (.6f * 255).toInt()
+
+ job.cancel()
+ }
+
+ @Test
+ fun transitionToFullShadeProgress() =
+ testScope.runTest {
+ // GIVEN view is attached and status bar expansion is 1f
+ mController.onViewAttached()
+ val job = mController.listenForBouncerExpansion(this)
+ keyguardBouncerRepository.setPrimaryShow(true)
+ keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_HIDDEN)
+ runCurrent()
+ Mockito.reset(mView)
+ whenever(mView.dialogSuggestedAlpha).thenReturn(1f)
+
+ // WHEN we're transitioning to the full shade
+ val transitionProgress = .6f
+ mController.setTransitionToFullShadeProgress(transitionProgress)
+
+ // THEN alpha is between 0 and 255
+ verify(mView).unpausedAlpha = ((1f - transitionProgress) * 255).toInt()
+
+ job.cancel()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
index 239e317..ed9ae5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -44,6 +45,7 @@
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class FingerprintRepositoryImplTest : SysuiTestCase() {
@@ -73,10 +75,15 @@
@Test
fun initializeProperties() =
testScope.runTest {
- val isInitialized = collectLastValue(repository.isInitialized)
+ val sensorId by collectLastValue(repository.sensorId)
+ val strength by collectLastValue(repository.strength)
+ val sensorType by collectLastValue(repository.sensorType)
+ val sensorLocations by collectLastValue(repository.sensorLocations)
- assertDefaultProperties()
- assertThat(isInitialized()).isFalse()
+ // Assert default properties.
+ assertThat(sensorId).isEqualTo(-1)
+ assertThat(strength).isEqualTo(SensorStrength.CONVENIENCE)
+ assertThat(sensorType).isEqualTo(FingerprintSensorType.UNKNOWN)
val fingerprintProps =
listOf(
@@ -115,31 +122,24 @@
fingerprintAuthenticatorsCaptor.value.onAllAuthenticatorsRegistered(fingerprintProps)
- assertThat(repository.sensorId.value).isEqualTo(1)
- assertThat(repository.strength.value).isEqualTo(SensorStrength.STRONG)
- assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.REAR)
+ assertThat(sensorId).isEqualTo(1)
+ assertThat(strength).isEqualTo(SensorStrength.STRONG)
+ assertThat(sensorType).isEqualTo(FingerprintSensorType.REAR)
- assertThat(repository.sensorLocations.value.size).isEqualTo(2)
- assertThat(repository.sensorLocations.value).containsKey("display_id_1")
- with(repository.sensorLocations.value["display_id_1"]!!) {
+ assertThat(sensorLocations?.size).isEqualTo(2)
+ assertThat(sensorLocations).containsKey("display_id_1")
+ with(sensorLocations?.get("display_id_1")!!) {
assertThat(displayId).isEqualTo("display_id_1")
assertThat(sensorLocationX).isEqualTo(100)
assertThat(sensorLocationY).isEqualTo(300)
assertThat(sensorRadius).isEqualTo(20)
}
- assertThat(repository.sensorLocations.value).containsKey("")
- with(repository.sensorLocations.value[""]!!) {
+ assertThat(sensorLocations).containsKey("")
+ with(sensorLocations?.get("")!!) {
assertThat(displayId).isEqualTo("")
assertThat(sensorLocationX).isEqualTo(540)
assertThat(sensorLocationY).isEqualTo(1636)
assertThat(sensorRadius).isEqualTo(130)
}
- assertThat(isInitialized()).isTrue()
}
-
- private fun assertDefaultProperties() {
- assertThat(repository.sensorId.value).isEqualTo(-1)
- assertThat(repository.strength.value).isEqualTo(SensorStrength.CONVENIENCE)
- assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.UNKNOWN)
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
index 2217c5c..524f254 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
@@ -1,9 +1,12 @@
package com.android.systemui.biometrics.domain.interactor
+import android.view.Display
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.display.data.repository.display
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.util.concurrency.FakeExecutor
@@ -34,19 +37,23 @@
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private val testScope = TestScope(StandardTestDispatcher())
- private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+ private lateinit var rearDisplayStateRepository: FakeRearDisplayStateRepository
+ private lateinit var displayRepository: FakeDisplayRepository
@Mock private lateinit var screenSizeFoldProvider: ScreenSizeFoldProvider
private lateinit var interactor: DisplayStateInteractorImpl
@Before
fun setup() {
+ rearDisplayStateRepository = FakeRearDisplayStateRepository()
+ displayRepository = FakeDisplayRepository()
interactor =
DisplayStateInteractorImpl(
testScope.backgroundScope,
mContext,
fakeExecutor,
- rearDisplayStateRepository
+ rearDisplayStateRepository,
+ displayRepository,
)
interactor.setScreenSizeFoldProvider(screenSizeFoldProvider)
}
@@ -76,6 +83,21 @@
callback.onFoldUpdated(isFolded = false)
assertThat(isFolded()).isFalse()
}
+
+ @Test
+ fun isDefaultDisplayOffChanges() =
+ testScope.runTest {
+ val isDefaultDisplayOff by collectLastValue(interactor.isDefaultDisplayOff)
+ runCurrent()
+
+ displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_OFF)))
+ displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+ assertThat(isDefaultDisplayOff).isTrue()
+
+ displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_ON)))
+ displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+ assertThat(isDefaultDisplayOff).isFalse()
+ }
}
private fun FoldProvider.captureCallback() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
index fd96cf4..712eef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
@@ -22,6 +22,7 @@
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -51,7 +52,7 @@
}
@Test
- fun testGetOverlayOffsets() =
+ fun testOverlayOffsetUpdates() =
testScope.runTest {
fingerprintRepository.setProperties(
sensorId = 1,
@@ -76,16 +77,32 @@
)
)
- var offsets = interactor.getOverlayOffsets("display_id_1")
- assertThat(offsets.displayId).isEqualTo("display_id_1")
- assertThat(offsets.sensorLocationX).isEqualTo(100)
- assertThat(offsets.sensorLocationY).isEqualTo(300)
- assertThat(offsets.sensorRadius).isEqualTo(20)
+ val displayId by collectLastValue(interactor.displayId)
+ val offsets by collectLastValue(interactor.overlayOffsets)
- offsets = interactor.getOverlayOffsets("invalid_display_id")
- assertThat(offsets.displayId).isEqualTo("")
- assertThat(offsets.sensorLocationX).isEqualTo(540)
- assertThat(offsets.sensorLocationY).isEqualTo(1636)
- assertThat(offsets.sensorRadius).isEqualTo(130)
+ // Assert offsets of empty displayId.
+ assertThat(displayId).isEqualTo("")
+ assertThat(offsets?.displayId).isEqualTo("")
+ assertThat(offsets?.sensorLocationX).isEqualTo(540)
+ assertThat(offsets?.sensorLocationY).isEqualTo(1636)
+ assertThat(offsets?.sensorRadius).isEqualTo(130)
+
+ // Offsets should be updated correctly.
+ interactor.onDisplayChanged("display_id_1")
+ assertThat(displayId).isEqualTo("display_id_1")
+ assertThat(offsets?.displayId).isEqualTo("display_id_1")
+ assertThat(offsets?.sensorLocationX).isEqualTo(100)
+ assertThat(offsets?.sensorLocationY).isEqualTo(300)
+ assertThat(offsets?.sensorRadius).isEqualTo(20)
+
+ // Should return default offset when the displayId is invalid.
+ interactor.onDisplayChanged("invalid_display_id")
+ assertThat(displayId).isEqualTo("invalid_display_id")
+ assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId)
+ assertThat(offsets?.sensorLocationX)
+ .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX)
+ assertThat(offsets?.sensorLocationY)
+ .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY)
+ assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
index 7697c09..b3964b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
@@ -14,6 +14,7 @@
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -39,9 +40,10 @@
@Mock private lateinit var lockPatternUtils: LockPatternUtils
- private val fingerprintRepository = FakeFingerprintPropertyRepository()
- private val promptRepository = FakePromptRepository()
- private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+ private lateinit var displayRepository: FakeDisplayRepository
+ private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
+ private lateinit var promptRepository: FakePromptRepository
+ private lateinit var rearDisplayStateRepository: FakeRearDisplayStateRepository
private val testScope = TestScope(StandardTestDispatcher())
private val fakeExecutor = FakeExecutor(FakeSystemClock())
@@ -52,6 +54,11 @@
@Before
fun setup() {
+ displayRepository = FakeDisplayRepository()
+ fingerprintRepository = FakeFingerprintPropertyRepository()
+ promptRepository = FakePromptRepository()
+ rearDisplayStateRepository = FakeRearDisplayStateRepository()
+
promptSelectorInteractor =
PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
displayStateInteractor =
@@ -59,7 +66,8 @@
testScope.backgroundScope,
mContext,
fakeExecutor,
- rearDisplayStateRepository
+ rearDisplayStateRepository,
+ displayRepository,
)
viewModel = PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 0ed46da..c03fd86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakePromptRepository
import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
@@ -38,6 +39,7 @@
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.statusbar.VibratorHelper
@@ -77,17 +79,12 @@
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private val testScope = TestScope()
- private val fingerprintRepository = FakeFingerprintPropertyRepository()
- private val promptRepository = FakePromptRepository()
- private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
- private val displayStateInteractor =
- DisplayStateInteractorImpl(
- testScope.backgroundScope,
- mContext,
- fakeExecutor,
- rearDisplayStateRepository
- )
+ private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
+ private lateinit var promptRepository: FakePromptRepository
+ private lateinit var rearDisplayStateRepository: FakeRearDisplayStateRepository
+ private lateinit var displayRepository: FakeDisplayRepository
+ private lateinit var displayStateInteractor: DisplayStateInteractor
private lateinit var selector: PromptSelectorInteractor
private lateinit var viewModel: PromptViewModel
@@ -95,6 +92,18 @@
@Before
fun setup() {
+ fingerprintRepository = FakeFingerprintPropertyRepository()
+ promptRepository = FakePromptRepository()
+ rearDisplayStateRepository = FakeRearDisplayStateRepository()
+ displayRepository = FakeDisplayRepository()
+ displayStateInteractor =
+ DisplayStateInteractorImpl(
+ testScope.backgroundScope,
+ mContext,
+ fakeExecutor,
+ rearDisplayStateRepository,
+ displayRepository,
+ )
selector =
PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
selector.resetPrompt()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index f892453..420fdba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -115,7 +115,7 @@
@Test
fun testShow_isScrimmed() {
underTest.show(true)
- verify(repository).setKeyguardAuthenticated(null)
+ verify(repository).setKeyguardAuthenticatedBiometrics(null)
verify(repository).setPrimaryStartingToHide(false)
verify(repository).setPrimaryScrimmed(true)
verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
@@ -222,8 +222,8 @@
@Test
fun testNotifyKeyguardAuthenticated() {
- underTest.notifyKeyguardAuthenticated(true)
- verify(repository).setKeyguardAuthenticated(true)
+ underTest.notifyKeyguardAuthenticatedBiometrics(true)
+ verify(repository).setKeyguardAuthenticatedBiometrics(true)
}
@Test
@@ -241,7 +241,7 @@
@Test
fun testNotifyKeyguardAuthenticatedHandled() {
underTest.notifyKeyguardAuthenticatedHandled()
- verify(repository).setKeyguardAuthenticated(null)
+ verify(repository).setKeyguardAuthenticatedBiometrics(null)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 4380af8..12090e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -185,6 +185,41 @@
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
+ @Test
+ fun onShown_againAfterSceneChange_resetsPassword() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
+ val password by collectLastValue(underTest.password)
+ utils.authenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
+ )
+ utils.authenticationRepository.setUnlocked(false)
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+
+ // The user types a password.
+ underTest.onPasswordInputChanged("password")
+ assertThat(password).isEqualTo("password")
+
+ // The user doesn't confirm the password, but navigates back to the lockscreen instead.
+ sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+
+ // The user navigates to the bouncer again.
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+ underTest.onShown()
+
+ // Ensure the previously-entered password is not shown.
+ assertThat(password).isEmpty()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
companion object {
private const val ENTER_YOUR_PASSWORD = "Enter your password"
private const val WRONG_PASSWORD = "Wrong password"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 531f86a..a684221 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -314,6 +314,42 @@
}
@Test
+ fun onShown_againAfterSceneChange_resetsPin() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
+ val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setUnlocked(false)
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+
+ // The user types a PIN.
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ underTest.onPinButtonClicked(digit)
+ }
+ assertThat(pin).isNotEmpty()
+
+ // The user doesn't confirm the PIN, but navigates back to the lockscreen instead.
+ sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+
+ // The user navigates to the bouncer again.
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+ underTest.onShown()
+
+ // Ensure the previously-entered PIN is not shown.
+ assertThat(pin).isEmpty()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
fun backspaceButtonAppearance_withoutAutoConfirm_alwaysShown() =
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 90076fd..ffcaeee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -17,7 +17,6 @@
package com.android.systemui.dreams.touch;
import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.eq;
@@ -295,7 +294,8 @@
}
/**
- * Makes sure the expansion amount is proportional to (1 - scroll).
+ * Verifies that swiping up when the lock pattern is not secure does not consume the scroll
+ * gesture or expand.
*/
@Test
public void testSwipeUp_keyguardNotSecure_doesNotExpand() {
@@ -314,8 +314,10 @@
0, SCREEN_HEIGHT_PX - distanceY, 0);
reset(mScrimController);
+
+ // Scroll gesture is not consumed.
assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
- .isTrue();
+ .isFalse();
// We should not expand since the keyguard is not secure
verify(mScrimController, never()).expand(any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index ec15416..f234582 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -29,6 +29,7 @@
import android.hardware.face.FaceSensorProperties
import android.hardware.face.FaceSensorPropertiesInternal
import android.os.CancellationSignal
+import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId.fakeInstanceId
@@ -41,11 +42,16 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
+import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.display.data.repository.display
import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
@@ -73,6 +79,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.captureMany
import com.android.systemui.util.mockito.mock
@@ -151,7 +158,9 @@
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var keyguardInteractor: KeyguardInteractor
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+ private lateinit var displayStateInteractor: DisplayStateInteractor
private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+ private lateinit var displayRepository: FakeDisplayRepository
private lateinit var fakeCommandQueue: FakeCommandQueue
private lateinit var featureFlags: FakeFeatureFlags
private lateinit var fakeFacePropertyRepository: FakeFacePropertyRepository
@@ -162,9 +171,10 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
fakeUserRepository = FakeUserRepository()
fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
- testDispatcher = StandardTestDispatcher()
biometricSettingsRepository = FakeBiometricSettingsRepository()
deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
trustRepository = FakeTrustRepository()
@@ -192,9 +202,18 @@
keyguardUpdateMonitor = keyguardUpdateMonitor,
)
+ displayRepository = FakeDisplayRepository()
+ displayStateInteractor =
+ DisplayStateInteractorImpl(
+ applicationScope = testScope.backgroundScope,
+ context = context,
+ mainExecutor = FakeExecutor(FakeSystemClock()),
+ rearDisplayStateRepository = FakeRearDisplayStateRepository(),
+ displayRepository = displayRepository,
+ )
+
bypassStateChangedListener =
KotlinArgumentCaptor(KeyguardBypassController.OnBypassStateChangedListener::class.java)
- testScope = TestScope(testDispatcher)
whenever(sessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(keyguardSessionId)
whenever(faceManager.sensorPropertiesInternal)
.thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
@@ -253,6 +272,7 @@
faceDetectBuffer,
faceAuthBuffer,
keyguardTransitionInteractor,
+ displayStateInteractor,
featureFlags,
dumpManager,
)
@@ -683,6 +703,44 @@
}
@Test
+ fun authenticateCanRunWhenDisplayIsOffAndWakingUp() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+
+ displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_OFF)))
+ displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.STARTING_TO_WAKE,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.POWER_BUTTON
+ )
+ )
+
+ assertThat(canFaceAuthRun()).isTrue()
+ }
+
+ @Test
+ fun authenticateDoesNotRunWhenDisplayIsOffAndAwake() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth {
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.POWER_BUTTON
+ )
+ )
+
+ displayRepository.emit(
+ setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_OFF))
+ )
+ displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+ }
+ }
+
+ @Test
fun everythingWorksWithFaceAuthRefactorFlagDisabled() =
testScope.runTest {
featureFlags.set(FACE_AUTH_REFACTOR, false)
@@ -1017,7 +1075,9 @@
faceAuthenticateIsCalled()
}
- private suspend fun TestScope.testGatingCheckForFaceAuth(gatingCheckModifier: () -> Unit) {
+ private suspend fun TestScope.testGatingCheckForFaceAuth(
+ gatingCheckModifier: suspend () -> Unit
+ ) {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
@@ -1108,6 +1168,8 @@
faceLockoutResetCallback.value.onLockoutReset(0)
bouncerRepository.setAlternateVisible(true)
keyguardRepository.setKeyguardShowing(true)
+ displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_ON)))
+ displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
runCurrent()
}
@@ -1120,6 +1182,7 @@
authenticated = collectLastValue(underTest.isAuthenticated)
bypassEnabled = collectLastValue(underTest.isBypassEnabled)
fakeUserRepository.setSelectedUserInfo(primaryUser)
+ runCurrent()
}
private fun successResult() = FaceManager.AuthenticationResult(null, null, primaryUserId, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
new file mode 100644
index 0000000..181cc88
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DismissAction
+import com.android.systemui.keyguard.shared.model.KeyguardDone
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardDismissActionInteractorTest : SysuiTestCase() {
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+ private lateinit var dispatcher: TestDispatcher
+ private lateinit var testScope: TestScope
+
+ private lateinit var dismissInteractorWithDependencies:
+ KeyguardDismissInteractorFactory.WithDependencies
+ private lateinit var underTest: KeyguardDismissActionInteractor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ dispatcher = StandardTestDispatcher()
+ testScope = TestScope(dispatcher)
+
+ dismissInteractorWithDependencies =
+ KeyguardDismissInteractorFactory.create(
+ context = context,
+ testScope = testScope,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ dispatcher = dispatcher,
+ )
+ keyguardRepository = dismissInteractorWithDependencies.keyguardRepository
+ transitionRepository = FakeKeyguardTransitionRepository()
+
+ underTest =
+ KeyguardDismissActionInteractor(
+ keyguardRepository,
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = transitionRepository,
+ )
+ .keyguardTransitionInteractor,
+ dismissInteractorWithDependencies.interactor,
+ testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun updateDismissAction_onRepoChange() =
+ testScope.runTest {
+ val dismissAction by collectLastValue(underTest.dismissAction)
+
+ val newDismissAction =
+ DismissAction.RunImmediately(
+ onDismissAction = { KeyguardDone.IMMEDIATE },
+ onCancelAction = {},
+ message = "",
+ willAnimateOnLockscreen = true,
+ )
+ keyguardRepository.setDismissAction(newDismissAction)
+ assertThat(dismissAction).isEqualTo(newDismissAction)
+ }
+
+ @Test
+ fun messageUpdate() =
+ testScope.runTest {
+ val message by collectLastValue(underTest.message)
+ keyguardRepository.setDismissAction(
+ DismissAction.RunImmediately(
+ onDismissAction = { KeyguardDone.IMMEDIATE },
+ onCancelAction = {},
+ message = "message",
+ willAnimateOnLockscreen = true,
+ )
+ )
+ assertThat(message).isEqualTo("message")
+ }
+
+ @Test
+ fun runDismissAnimationOnKeyguard_defaultStateFalse() =
+ testScope.runTest { assertThat(underTest.runDismissAnimationOnKeyguard()).isFalse() }
+
+ @Test
+ fun runDismissAnimationOnKeyguardUpdates() =
+ testScope.runTest {
+ val animate by collectLastValue(underTest.willAnimateDismissActionOnLockscreen)
+ keyguardRepository.setDismissAction(
+ DismissAction.RunImmediately(
+ onDismissAction = { KeyguardDone.IMMEDIATE },
+ onCancelAction = {},
+ message = "message",
+ willAnimateOnLockscreen = true,
+ )
+ )
+ assertThat(animate).isEqualTo(true)
+
+ keyguardRepository.setDismissAction(
+ DismissAction.RunImmediately(
+ onDismissAction = { KeyguardDone.IMMEDIATE },
+ onCancelAction = {},
+ message = "message",
+ willAnimateOnLockscreen = false,
+ )
+ )
+ assertThat(animate).isEqualTo(false)
+ }
+
+ @Test
+ fun executeDismissAction_dismissKeyguardRequestWithImmediateDismissAction_biometricAuthed() =
+ testScope.runTest {
+ val executeDismissAction by collectLastValue(underTest.executeDismissAction)
+
+ val onDismissAction = { KeyguardDone.IMMEDIATE }
+ keyguardRepository.setDismissAction(
+ DismissAction.RunImmediately(
+ onDismissAction = onDismissAction,
+ onCancelAction = {},
+ message = "message",
+ willAnimateOnLockscreen = true,
+ )
+ )
+ dismissInteractorWithDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics(
+ true
+ )
+ assertThat(executeDismissAction).isEqualTo(onDismissAction)
+ }
+
+ @Test
+ fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction() =
+ testScope.runTest {
+ val executeDismissAction by collectLastValue(underTest.executeDismissAction)
+
+ // WHEN a keyguard action will run after the keyguard is gone
+ val onDismissAction = {}
+ keyguardRepository.setDismissAction(
+ DismissAction.RunAfterKeyguardGone(
+ dismissAction = onDismissAction,
+ onCancelAction = {},
+ message = "message",
+ willAnimateOnLockscreen = true,
+ )
+ )
+ assertThat(executeDismissAction).isNull()
+
+ // WHEN the keyguard is GONE
+ transitionRepository.sendTransitionStep(
+ TransitionStep(to = KeyguardState.GONE, transitionState = TransitionState.FINISHED)
+ )
+ assertThat(executeDismissAction).isNotNull()
+ }
+
+ @Test
+ fun resetDismissAction() =
+ testScope.runTest {
+ val resetDismissAction by collectLastValue(underTest.resetDismissAction)
+
+ keyguardRepository.setDismissAction(
+ DismissAction.RunAfterKeyguardGone(
+ dismissAction = {},
+ onCancelAction = {},
+ message = "message",
+ willAnimateOnLockscreen = true,
+ )
+ )
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+ assertThat(resetDismissAction).isEqualTo(Unit)
+ }
+
+ @Test
+ fun setDismissAction_callsCancelRunnableOnPreviousDismissAction() =
+ testScope.runTest {
+ val dismissAction by collectLastValue(underTest.dismissAction)
+ var previousDismissActionCancelCalled = false
+ keyguardRepository.setDismissAction(
+ DismissAction.RunImmediately(
+ onDismissAction = { KeyguardDone.IMMEDIATE },
+ onCancelAction = { previousDismissActionCancelCalled = true },
+ message = "",
+ willAnimateOnLockscreen = true,
+ )
+ )
+
+ val newDismissAction =
+ DismissAction.RunImmediately(
+ onDismissAction = { KeyguardDone.IMMEDIATE },
+ onCancelAction = {},
+ message = "",
+ willAnimateOnLockscreen = true,
+ )
+ underTest.setDismissAction(newDismissAction)
+
+ // THEN previous dismiss action got its onCancel called
+ assertThat(previousDismissActionCancelCalled).isTrue()
+
+ // THEN dismiss action is updated
+ assertThat(dismissAction).isEqualTo(newDismissAction)
+ }
+
+ @Test
+ fun handleDismissAction() =
+ testScope.runTest {
+ val dismissAction by collectLastValue(underTest.dismissAction)
+ underTest.handleDismissAction()
+ assertThat(dismissAction).isEqualTo(DismissAction.None)
+ }
+
+ @Test
+ fun setKeyguardDone() =
+ testScope.runTest {
+ val keyguardDoneTiming by
+ collectLastValue(dismissInteractorWithDependencies.interactor.keyguardDone)
+ runCurrent()
+
+ underTest.setKeyguardDone(KeyguardDone.LATER)
+ assertThat(keyguardDoneTiming).isEqualTo(KeyguardDone.LATER)
+
+ underTest.setKeyguardDone(KeyguardDone.IMMEDIATE)
+ assertThat(keyguardDoneTiming).isEqualTo(KeyguardDone.IMMEDIATE)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
new file mode 100644
index 0000000..c407b14
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.pm.UserInfo
+import android.service.trust.TrustAgentService
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.TrustGrantFlags
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.shared.model.DismissAction
+import com.android.systemui.keyguard.shared.model.KeyguardDone
+import com.android.systemui.keyguard.shared.model.TrustModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardDismissInteractorTest : SysuiTestCase() {
+ private lateinit var dispatcher: TestDispatcher
+ private lateinit var testScope: TestScope
+
+ private lateinit var underTestDependencies: KeyguardDismissInteractorFactory.WithDependencies
+ private lateinit var underTest: KeyguardDismissInteractor
+ private val userInfo = UserInfo(0, "", 0)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ dispatcher = StandardTestDispatcher()
+ testScope = TestScope(dispatcher)
+
+ underTestDependencies =
+ KeyguardDismissInteractorFactory.create(
+ context = context,
+ testScope = testScope,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ dispatcher = dispatcher,
+ )
+ underTest = underTestDependencies.interactor
+ underTestDependencies.userRepository.setUserInfos(listOf(userInfo))
+ }
+
+ @Test
+ fun biometricAuthenticatedRequestDismissKeyguard_noDismissAction() =
+ testScope.runTest {
+ val dismissKeyguardRequestWithoutImmediateDismissAction by
+ collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
+
+ underTestDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics(null)
+ assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
+
+ underTestDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics(true)
+ assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit)
+ }
+
+ @Test
+ fun onTrustGrantedRequestDismissKeyguard_noDismissAction() =
+ testScope.runTest {
+ val dismissKeyguardRequestWithoutImmediateDismissAction by
+ collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
+ underTestDependencies.trustRepository.setRequestDismissKeyguard(
+ TrustModel(
+ true,
+ 0,
+ TrustGrantFlags(0),
+ )
+ )
+ assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
+
+ underTestDependencies.powerRepository.setInteractive(true)
+ underTestDependencies.trustRepository.setRequestDismissKeyguard(
+ TrustModel(
+ true,
+ 0,
+ TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD),
+ )
+ )
+ assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit)
+ }
+
+ @Test
+ fun primaryAuthenticated_noDismissAction() =
+ testScope.runTest {
+ val dismissKeyguardRequestWithoutImmediateDismissAction by
+ collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
+ underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+ runCurrent()
+
+ // authenticated different user
+ underTestDependencies.bouncerRepository.setKeyguardAuthenticatedPrimaryAuth(22)
+ assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
+
+ // authenticated correct user
+ underTestDependencies.bouncerRepository.setKeyguardAuthenticatedPrimaryAuth(userInfo.id)
+ assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit)
+ }
+
+ @Test
+ fun userRequestedBouncerWhenAlreadyAuthenticated_noDismissAction() =
+ testScope.runTest {
+ val dismissKeyguardRequestWithoutImmediateDismissAction by
+ collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
+ underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+ runCurrent()
+
+ // requested from different user
+ underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
+ 22
+ )
+ assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
+
+ // requested from correct user
+ underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
+ userInfo.id
+ )
+ assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit)
+ }
+
+ @Test
+ fun keyguardDone() =
+ testScope.runTest {
+ val keyguardDone by collectLastValue(underTest.keyguardDone)
+ assertThat(keyguardDone).isNull()
+
+ underTest.setKeyguardDone(KeyguardDone.IMMEDIATE)
+ assertThat(keyguardDone).isEqualTo(KeyguardDone.IMMEDIATE)
+
+ underTest.setKeyguardDone(KeyguardDone.LATER)
+ assertThat(keyguardDone).isEqualTo(KeyguardDone.LATER)
+ }
+
+ @Test
+ fun userRequestedBouncerWhenAlreadyAuthenticated_dismissActionRunImmediately() =
+ testScope.runTest {
+ val dismissKeyguardRequestWithoutImmediateDismissAction by
+ collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
+ val dismissKeyguardRequestWithImmediateDismissAction by
+ collectLastValue(underTest.dismissKeyguardRequestWithImmediateDismissAction)
+ underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+ runCurrent()
+
+ underTestDependencies.keyguardRepository.setDismissAction(
+ DismissAction.RunImmediately(
+ onDismissAction = { KeyguardDone.IMMEDIATE },
+ onCancelAction = {},
+ message = "",
+ willAnimateOnLockscreen = true,
+ )
+ )
+ underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
+ userInfo.id
+ )
+ assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
+ assertThat(dismissKeyguardRequestWithImmediateDismissAction).isEqualTo(Unit)
+ }
+
+ @Test
+ fun userRequestedBouncerWhenAlreadyAuthenticated_dismissActionRunAfterKeyguardGone() =
+ testScope.runTest {
+ val dismissKeyguardRequestWithImmediateWithoutDismissAction by
+ collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
+ val dismissKeyguardRequestWithImmediateDismissAction by
+ collectLastValue(underTest.dismissKeyguardRequestWithImmediateDismissAction)
+ underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+ runCurrent()
+
+ underTestDependencies.keyguardRepository.setDismissAction(
+ DismissAction.RunAfterKeyguardGone(
+ dismissAction = {},
+ onCancelAction = {},
+ message = "",
+ willAnimateOnLockscreen = true,
+ )
+ )
+ underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
+ userInfo.id
+ )
+ assertThat(dismissKeyguardRequestWithImmediateDismissAction).isNull()
+ assertThat(dismissKeyguardRequestWithImmediateWithoutDismissAction).isEqualTo(Unit)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index bdcb9ab..b81a3d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -159,7 +159,6 @@
screenOffAnimationController = mock(),
statusBarStateController = mock(),
),
- FakeFeatureFlags().apply { set(Flags.FP_LISTEN_OCCLUDING_APPS, true) },
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
index bb73dc6..dbf6a29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
@@ -81,4 +81,10 @@
command().execute(pw, listOf("fake"))
verify(keyguardBlueprintInteractor).transitionToBlueprint("fake")
}
+
+ @Test
+ fun testValidArg_Int() {
+ command().execute(pw, listOf("1"))
+ verify(keyguardBlueprintInteractor).transitionToBlueprint(1)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 904662e..da372ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -22,7 +22,10 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.ScrimAlpha
@@ -32,6 +35,7 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -47,13 +51,21 @@
class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: PrimaryBouncerToGoneTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var featureFlags: FakeFeatureFlags
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+ @Mock
+ private lateinit var keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
repository = FakeKeyguardTransitionRepository()
+ val featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+ set(Flags.UDFPS_NEW_TOUCH_DETECTION, true)
+ }
val interactor =
KeyguardTransitionInteractorFactory.create(
scope = TestScope().backgroundScope,
@@ -64,7 +76,9 @@
PrimaryBouncerToGoneTransitionViewModel(
interactor,
statusBarStateController,
- primaryBouncerInteractor
+ primaryBouncerInteractor,
+ keyguardDismissActionInteractor,
+ featureFlags,
)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index cbfad56..48a36cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -30,6 +30,8 @@
import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -93,7 +95,6 @@
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -330,6 +331,58 @@
}
@Test
+ public void onHomeTouch_isRinging_keyguardShowing_touchBlocked() {
+ when(mTelecomManager.isRinging()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+
+ boolean result = mNavigationBar.onHomeTouch(
+ mNavigationBar.getView(),
+ MotionEvent.obtain(
+ /*downTime=*/SystemClock.uptimeMillis(),
+ /*eventTime=*/SystemClock.uptimeMillis(),
+ /*action=*/MotionEvent.ACTION_DOWN,
+ 0, 0, 0));
+
+ assertThat(result).isTrue();
+
+ // Verify subsequent touches are also blocked
+ boolean nextTouchEvent = mNavigationBar.onHomeTouch(
+ mNavigationBar.getView(),
+ MotionEvent.obtain(
+ /*downTime=*/SystemClock.uptimeMillis(),
+ /*eventTime=*/SystemClock.uptimeMillis(),
+ /*action=*/MotionEvent.ACTION_MOVE,
+ 0, 0, 0));
+ assertThat(nextTouchEvent).isTrue();
+ }
+
+ @Test
+ public void onHomeTouch_isRinging_keyguardNotShowing_touchNotBlocked() {
+ when(mTelecomManager.isRinging()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+ boolean result = mNavigationBar.onHomeTouch(
+ mNavigationBar.getView(),
+ MotionEvent.obtain(
+ /*downTime=*/SystemClock.uptimeMillis(),
+ /*eventTime=*/SystemClock.uptimeMillis(),
+ /*action=*/MotionEvent.ACTION_DOWN,
+ 0, 0, 0));
+
+ assertThat(result).isFalse();
+
+ // Verify subsequent touches are also not blocked
+ boolean nextTouchEvent = mNavigationBar.onHomeTouch(
+ mNavigationBar.getView(),
+ MotionEvent.obtain(
+ /*downTime=*/SystemClock.uptimeMillis(),
+ /*eventTime=*/SystemClock.uptimeMillis(),
+ /*action=*/MotionEvent.ACTION_MOVE,
+ 0, 0, 0));
+ assertThat(nextTouchEvent).isFalse();
+ }
+
+ @Test
public void testRegisteredWithUserTracker() {
mNavigationBar.init();
mNavigationBar.onViewAttached();
@@ -468,7 +521,6 @@
when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
return spy(new NavigationBar(
mNavigationBarView,
- mock(ShadeController.class),
mNavigationBarFrame,
null,
context,
@@ -487,6 +539,7 @@
Optional.of(mock(Pip.class)),
Optional.of(mock(Recents.class)),
() -> Optional.of(mCentralSurfaces),
+ mKeyguardStateController,
mock(ShadeViewController.class),
mock(NotificationRemoteInputManager.class),
mock(NotificationShadeDepthController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
index 77b3e69f..9386d71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
@@ -17,8 +17,9 @@
package com.android.systemui.qs.pipeline.data.repository
import android.provider.Settings
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -34,7 +35,8 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class AutoAddSettingsRepositoryTest : SysuiTestCase() {
private val secureSettings = FakeSettings()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
index d7ab903..30f5811 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
@@ -18,8 +18,9 @@
import android.content.ComponentName
import android.content.SharedPreferences
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.UserFileManager
import com.android.systemui.util.FakeSharedPreferences
@@ -29,7 +30,8 @@
import org.junit.runner.RunWith
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class CustomTileAddedSharedPreferencesRepositoryTest : SysuiTestCase() {
private lateinit var underTest: CustomTileAddedSharedPrefsRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
index dc0fae5..995de66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -29,8 +29,10 @@
import android.content.pm.ServiceInfo
import android.os.UserHandle
import android.service.quicksettings.TileService
-import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.util.mockito.any
@@ -60,7 +62,9 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
@OptIn(ExperimentalCoroutinesApi::class)
class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 72c31b1..aef5faf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -17,9 +17,10 @@
package com.android.systemui.qs.pipeline.data.repository
import android.provider.Settings
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.QSHost
@@ -41,7 +42,8 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class TileSpecSettingsRepositoryTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
index 817ac61..6e579d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
@@ -17,9 +17,10 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
import android.content.ComponentName
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.util.mockito.mock
@@ -28,7 +29,8 @@
import org.junit.runner.RunWith
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class AutoAddableSettingListTest : SysuiTestCase() {
private val factory =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
index 36c3c9d..7c6dd24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
@@ -16,8 +16,9 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -35,7 +36,8 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class AutoAddableSettingTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
index afb43c7..469eee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
@@ -16,8 +16,9 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
@@ -35,7 +36,8 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class CallbackControllerAutoAddableTest : SysuiTestCase() {
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
index a357dad..b6eaa39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
@@ -16,8 +16,9 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
@@ -41,7 +42,8 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class CastAutoAddableTest : SysuiTestCase() {
@Mock private lateinit var castController: CastController
@@ -128,6 +130,6 @@
}
companion object {
- private val SPEC = TileSpec.create(CastTile.TILE_SPEC)
+ private val SPEC by lazy { TileSpec.create(CastTile.TILE_SPEC) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
index 098ffc3..a755fbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
@@ -16,8 +16,9 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
@@ -40,7 +41,8 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class DataSaverAutoAddableTest : SysuiTestCase() {
@Mock private lateinit var dataSaverController: DataSaverController
@@ -80,6 +82,6 @@
}
companion object {
- private val SPEC = TileSpec.create(DataSaverTile.TILE_SPEC)
+ private val SPEC by lazy { TileSpec.create(DataSaverTile.TILE_SPEC) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
index a2e3538..daacca51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
@@ -16,8 +16,9 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
@@ -43,7 +44,8 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class DeviceControlsAutoAddableTest : SysuiTestCase() {
@Mock private lateinit var deviceControlsController: DeviceControlsController
@@ -110,6 +112,6 @@
}
companion object {
- private val SPEC = TileSpec.create(DeviceControlsTile.TILE_SPEC)
+ private val SPEC by lazy { TileSpec.create(DeviceControlsTile.TILE_SPEC) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
index ee96b47..4b5f7f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
@@ -16,8 +16,9 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
@@ -40,7 +41,8 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class HotspotAutoAddableTest : SysuiTestCase() {
@Mock private lateinit var hotspotController: HotspotController
@@ -78,6 +80,6 @@
}
companion object {
- private val SPEC = TileSpec.create(HotspotTile.TILE_SPEC)
+ private val SPEC by lazy { TileSpec.create(HotspotTile.TILE_SPEC) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
index e03072a..32d9db2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
@@ -17,8 +17,9 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
import android.hardware.display.NightDisplayListener
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dagger.NightDisplayListenerModule
@@ -49,7 +50,8 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class NightDisplayAutoAddableTest : SysuiTestCase() {
@Mock(answer = Answers.RETURNS_SELF)
@@ -121,6 +123,6 @@
}
companion object {
- private val SPEC = TileSpec.create(NightDisplayTile.TILE_SPEC)
+ private val SPEC by lazy { TileSpec.create(NightDisplayTile.TILE_SPEC) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
index 7b4a55e..fb513a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
@@ -16,8 +16,9 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.ReduceBrightColorsController
@@ -43,7 +44,8 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class ReduceBrightColorsAutoAddableTest : SysuiTestCase() {
@Mock private lateinit var reduceBrightColorsController: ReduceBrightColorsController
@@ -103,6 +105,6 @@
}
companion object {
- private val SPEC = TileSpec.create(ReduceBrightColorsTile.TILE_SPEC)
+ private val SPEC by lazy { TileSpec.create(ReduceBrightColorsTile.TILE_SPEC) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
index fb35a3a..8036cb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
@@ -18,9 +18,10 @@
import android.content.ComponentName
import android.content.pm.PackageManager
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -51,7 +52,8 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class SafetyCenterAutoAddableTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -155,9 +157,10 @@
companion object {
private const val SAFETY_TILE_CLASS_NAME = "cls"
private const val PERMISSION_CONTROLLER_PACKAGE_NAME = "pkg"
- private val SPEC =
+ private val SPEC by lazy {
TileSpec.create(
ComponentName(PERMISSION_CONTROLLER_PACKAGE_NAME, SAFETY_TILE_CLASS_NAME)
)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
index 6b250f4..1c8cb54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
@@ -16,8 +16,9 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
@@ -37,7 +38,8 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class WalletAutoAddableTest : SysuiTestCase() {
@Mock private lateinit var walletController: WalletController
@@ -76,6 +78,6 @@
}
companion object {
- private val SPEC = TileSpec.create(QuickAccessWalletTile.TILE_SPEC)
+ private val SPEC by lazy { TileSpec.create(QuickAccessWalletTile.TILE_SPEC) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
index e9f7c8ab..de1d29fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
@@ -21,8 +21,9 @@
import android.content.pm.UserInfo.FLAG_MANAGED_PROFILE
import android.content.pm.UserInfo.FLAG_PRIMARY
import android.content.pm.UserInfo.FLAG_PROFILE
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
@@ -40,7 +41,8 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class WorkTileAutoAddableTest : SysuiTestCase() {
private lateinit var userTracker: FakeUserTracker
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
index f924b35..bb18115 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
@@ -16,8 +16,9 @@
package com.android.systemui.qs.pipeline.domain.interactor
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dump.DumpManager
@@ -46,7 +47,8 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class AutoAddInteractorTest : SysuiTestCase() {
private val testScope = TestScope()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 54a9360..dc1b9c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -22,8 +22,9 @@
import android.content.pm.UserInfo
import android.os.UserHandle
import android.service.quicksettings.Tile
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dump.nano.SystemUIProtoDump
@@ -69,7 +70,8 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class CurrentTilesInteractorImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
index 6556cfd..151b256 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
@@ -15,8 +15,9 @@
*/
package com.android.systemui.qs.pipeline.domain.interactor
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.shade.ShadeController
import org.junit.Before
@@ -26,7 +27,8 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
@SmallTest
class PanelInteractorImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
index d880172..34c4c98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
@@ -17,15 +17,17 @@
package com.android.systemui.qs.pipeline.shared
import android.content.ComponentName
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
class TileSpecTest : SysuiTestCase() {
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 5e7f68c..e5c55d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -168,7 +168,7 @@
val btDevice = mock<BluetoothDevice>()
whenever(cachedDevice2.device).thenReturn(btDevice)
whenever(btDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(null)
- whenever(cachedDevice2.batteryLevel).thenReturn(25)
+ whenever(cachedDevice2.minBatteryLevelWithMemberDevices).thenReturn(25)
addConnectedDevice(cachedDevice2)
tile.handleUpdateState(state, /* arg= */ null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 1e47f78..0d694ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -51,30 +51,6 @@
/** Tests the Java-compatible function wrapper, ensures callback is invoked. */
@Test
- fun testProcessAsync() {
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_KEY_OTHER)
- .setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
- .build()
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
-
- var result: ScreenshotRequest? = null
- var callbackCount = 0
- val callback: (ScreenshotRequest) -> Unit = { processedRequest: ScreenshotRequest ->
- result = processedRequest
- callbackCount++
- }
-
- // runs synchronously, using Unconfined Dispatcher
- processor.processAsync(request, callback)
-
- // Callback invoked once returning the same request (no changes)
- assertThat(callbackCount).isEqualTo(1)
- assertThat(result).isEqualTo(request)
- }
-
- /** Tests the Java-compatible function wrapper, ensures callback is invoked. */
- @Test
fun testProcessAsync_ScreenshotData() {
val request =
ScreenshotData.fromRequest(
@@ -112,13 +88,6 @@
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
- val processedRequest = processor.process(request)
-
- // Request has topComponent added, but otherwise unchanged.
- assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
- assertThat(processedRequest.source).isEqualTo(SCREENSHOT_OTHER)
- assertThat(processedRequest.topComponent).isEqualTo(component)
-
val processedData = processor.process(ScreenshotData.fromRequest(request))
// Request has topComponent added, but otherwise unchanged.
@@ -144,18 +113,6 @@
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
- val processedRequest = processor.process(request)
-
- // Expect a task snapshot is taken, overriding the full screen mode
- assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
- assertThat(bitmap.equalsHardwareBitmap(processedRequest.bitmap)).isTrue()
- assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
- assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
- assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
- assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
- assertThat(processedRequest.userId).isEqualTo(USER_ID)
- assertThat(processedRequest.topComponent).isEqualTo(component)
-
val processedData = processor.process(ScreenshotData.fromRequest(request))
// Expect a task snapshot is taken, overriding the full screen mode
@@ -165,8 +122,6 @@
assertThat(processedData.insets).isEqualTo(Insets.NONE)
assertThat(processedData.taskId).isEqualTo(TASK_ID)
assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
- assertThat(processedRequest.userId).isEqualTo(USER_ID)
- assertThat(processedRequest.topComponent).isEqualTo(component)
}
@Test
@@ -186,9 +141,6 @@
val processor = RequestProcessor(imageCapture, policy, flags, scope)
Assert.assertThrows(IllegalStateException::class.java) {
- runBlocking { processor.process(request) }
- }
- Assert.assertThrows(IllegalStateException::class.java) {
runBlocking { processor.process(ScreenshotData.fromRequest(request)) }
}
}
@@ -212,11 +164,6 @@
.setInsets(Insets.NONE)
.build()
- val processedRequest = processor.process(request)
-
- // No changes
- assertThat(processedRequest).isEqualTo(request)
-
val screenshotData = ScreenshotData.fromRequest(request)
val processedData = processor.process(screenshotData)
@@ -243,14 +190,10 @@
.setInsets(Insets.NONE)
.build()
- val processedRequest = processor.process(request)
-
- // Work profile, but already a task snapshot, so no changes
- assertThat(processedRequest).isEqualTo(request)
-
val screenshotData = ScreenshotData.fromRequest(request)
val processedData = processor.process(screenshotData)
+ // Work profile, but already a task snapshot, so no changes
assertThat(processedData).isEqualTo(screenshotData)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 97c2ed4..cfdf66e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -1,6 +1,7 @@
package com.android.systemui.screenshot
import android.content.ComponentName
+import android.graphics.Bitmap
import android.net.Uri
import android.testing.AndroidTestingRunner
import android.view.Display
@@ -10,6 +11,7 @@
import android.view.Display.TYPE_VIRTUAL
import android.view.Display.TYPE_WIFI
import android.view.WindowManager
+import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.util.ScreenshotRequest
@@ -95,6 +97,35 @@
}
@Test
+ fun executeScreenshots_providedImageType_callsOnlyDefaultDisplayController() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ screenshotExecutor.executeScreenshots(
+ createScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE),
+ onSaved,
+ callback
+ )
+
+ verify(controllerFactory).create(eq(0))
+ verify(controllerFactory, never()).create(eq(1))
+
+ val capturer = ArgumentCaptor<ScreenshotData>()
+
+ verify(controller0).handleScreenshot(capturer.capture(), any(), any())
+ assertThat(capturer.value.displayId).isEqualTo(0)
+ // OnSaved callback should be different.
+ verify(controller1, never()).handleScreenshot(any(), any(), any())
+
+ assertThat(eventLogger.numLogs()).isEqualTo(1)
+ assertThat(eventLogger.get(0).eventId)
+ .isEqualTo(ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id)
+ assertThat(eventLogger.get(0).packageName).isEqualTo(topComponent.packageName)
+
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
fun executeScreenshots_onlyVirtualDisplays_noInteractionsWithControllers() =
testScope.runTest {
setDisplays(display(TYPE_VIRTUAL, id = 0), display(TYPE_VIRTUAL, id = 1))
@@ -283,12 +314,14 @@
runCurrent()
}
- private fun createScreenshotRequest() =
- ScreenshotRequest.Builder(
- WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
- WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
- )
+ private fun createScreenshotRequest(type: Int = WindowManager.TAKE_SCREENSHOT_FULLSCREEN) =
+ ScreenshotRequest.Builder(type, WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER)
.setTopComponent(topComponent)
+ .also {
+ if (type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ it.setBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
+ }
+ }
.build()
private class FakeRequestProcessor : ScreenshotRequestProcessor {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index a08cda6..6205d90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -20,10 +20,6 @@
import android.app.admin.DevicePolicyResources.Strings.SystemUi.SCREENSHOT_BLOCKED_BY_ADMIN
import android.app.admin.DevicePolicyResourcesManager
import android.content.ComponentName
-import android.graphics.Bitmap
-import android.graphics.Bitmap.Config.HARDWARE
-import android.graphics.ColorSpace
-import android.hardware.HardwareBuffer
import android.os.UserHandle
import android.os.UserManager
import android.testing.AndroidTestingRunner
@@ -94,14 +90,6 @@
// Stub request processor as a synchronous no-op for tests with the flag enabled
doAnswer {
- val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
- val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
- consumer.accept(request)
- }
- .whenever(requestProcessor)
- .processAsync(/* request= */ any(ScreenshotRequest::class.java), /* callback= */ any())
-
- doAnswer {
val request: ScreenshotData = it.getArgument(0) as ScreenshotData
val consumer: Consumer<ScreenshotData> = it.getArgument(1)
consumer.accept(request)
@@ -353,23 +341,3 @@
return service
}
}
-
-private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean {
- return config == HARDWARE &&
- other.config == HARDWARE &&
- hardwareBuffer == other.hardwareBuffer &&
- colorSpace == other.colorSpace
-}
-
-/** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */
-private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
- val buffer =
- HardwareBuffer.create(
- width,
- height,
- HardwareBuffer.RGBA_8888,
- 1,
- HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
- )
- return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index e42a7a6..46b636b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -34,7 +34,6 @@
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.TestScopeProvider;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
@@ -48,6 +47,8 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.QSFragment;
+import com.android.systemui.scene.SceneTestUtils;
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -99,7 +100,8 @@
protected QuickSettingsController mQsController;
- protected TestScope mTestScope = TestScopeProvider.getTestScope();
+ protected SceneTestUtils mUtils = new SceneTestUtils(this);
+ protected TestScope mTestScope = mUtils.getTestScope();
@Mock
protected Resources mResources;
@@ -172,6 +174,8 @@
new ShadeInteractor(
mTestScope.getBackgroundScope(),
mDisableFlagsRepository,
+ new FakeSceneContainerFlags(),
+ () -> mUtils.sceneInteractor(),
mKeyguardRepository,
new FakeUserSetupRepository(),
mDeviceProvisionedController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index ba5ecce..dd6ab73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -35,6 +35,10 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
@@ -52,9 +56,8 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -67,9 +70,11 @@
class ShadeInteractorTest : SysuiTestCase() {
private lateinit var underTest: ShadeInteractor
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
private val featureFlags = FakeFeatureFlags()
+ private val sceneContainerFlags = FakeSceneContainerFlags()
+ private val sceneInteractor = utils.sceneInteractor()
private val userSetupRepository = FakeUserSetupRepository()
private val userRepository = FakeUserRepository()
private val disableFlagsRepository = FakeDisableFlagsRepository()
@@ -103,7 +108,7 @@
val refreshUsersScheduler =
RefreshUsersScheduler(
applicationScope = testScope.backgroundScope,
- mainDispatcher = testDispatcher,
+ mainDispatcher = utils.testDispatcher,
repository = userRepository,
)
@@ -141,7 +146,7 @@
),
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
- backgroundDispatcher = testDispatcher,
+ backgroundDispatcher = utils.testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestInteractor,
@@ -151,6 +156,8 @@
ShadeInteractor(
testScope.backgroundScope,
disableFlagsRepository,
+ sceneContainerFlags,
+ { sceneInteractor },
keyguardRepository,
userSetupRepository,
deviceProvisionedController,
@@ -557,4 +564,146 @@
// THEN anyExpanding is false
assertThat(actual).isFalse()
}
+
+ @Test
+ fun lockscreenShadeExpansion_idle_onScene() =
+ testScope.runTest() {
+ // GIVEN an expansion flow based on transitions to and from a scene
+ val key = SceneKey.Shade
+ val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
+ val expansionAmount by collectLastValue(expansion)
+
+ // WHEN transition state is idle on the scene
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN expansion is 1
+ assertThat(expansionAmount).isEqualTo(1f)
+ }
+
+ @Test
+ fun lockscreenShadeExpansion_idle_onDifferentScene() =
+ testScope.runTest() {
+ // GIVEN an expansion flow based on transitions to and from a scene
+ val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.Shade)
+ val expansionAmount by collectLastValue(expansion)
+
+ // WHEN transition state is idle on a different scene
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(SceneKey.Lockscreen)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN expansion is 0
+ assertThat(expansionAmount).isEqualTo(0f)
+ }
+
+ @Test
+ fun lockscreenShadeExpansion_transitioning_toScene() =
+ testScope.runTest() {
+ // GIVEN an expansion flow based on transitions to and from a scene
+ val key = SceneKey.QuickSettings
+ val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
+ val expansionAmount by collectLastValue(expansion)
+
+ // WHEN transition state is starting to move to the scene
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Lockscreen,
+ toScene = key,
+ progress = progress,
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN expansion is 0
+ assertThat(expansionAmount).isEqualTo(0f)
+
+ // WHEN transition state is partially to the scene
+ progress.value = .4f
+
+ // THEN expansion matches the progress
+ assertThat(expansionAmount).isEqualTo(.4f)
+
+ // WHEN transition completes
+ progress.value = 1f
+
+ // THEN expansion is 1
+ assertThat(expansionAmount).isEqualTo(1f)
+ }
+
+ @Test
+ fun lockscreenShadeExpansion_transitioning_fromScene() =
+ testScope.runTest() {
+ // GIVEN an expansion flow based on transitions to and from a scene
+ val key = SceneKey.QuickSettings
+ val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
+ val expansionAmount by collectLastValue(expansion)
+
+ // WHEN transition state is starting to move to the scene
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = key,
+ toScene = SceneKey.Lockscreen,
+ progress = progress,
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN expansion is 1
+ assertThat(expansionAmount).isEqualTo(1f)
+
+ // WHEN transition state is partially to the scene
+ progress.value = .4f
+
+ // THEN expansion reflects the progress
+ assertThat(expansionAmount).isEqualTo(.6f)
+
+ // WHEN transition completes
+ progress.value = 1f
+
+ // THEN expansion is 0
+ assertThat(expansionAmount).isEqualTo(0f)
+ }
+
+ @Test
+ fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() =
+ testScope.runTest() {
+ // GIVEN an expansion flow based on transitions to and from a scene
+ val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings)
+ val expansionAmount by collectLastValue(expansion)
+
+ // WHEN transition state is starting to between different scenes
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Lockscreen,
+ toScene = SceneKey.Shade,
+ progress = progress,
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN expansion is 0
+ assertThat(expansionAmount).isEqualTo(0f)
+
+ // WHEN transition state is partially complete
+ progress.value = .4f
+
+ // THEN expansion is still 0
+ assertThat(expansionAmount).isEqualTo(0f)
+
+ // WHEN transition completes
+ progress.value = 1f
+
+ // THEN expansion is still 0
+ assertThat(expansionAmount).isEqualTo(0f)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 6f75880..2446234 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -20,6 +20,8 @@
import com.android.systemui.plugins.qs.QS
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -39,8 +41,6 @@
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import org.junit.After
import org.junit.Assert.assertFalse
@@ -74,8 +74,8 @@
@RunWith(AndroidTestingRunner::class)
@OptIn(ExperimentalCoroutinesApi::class)
class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
-
- private val testScope = TestScope(StandardTestDispatcher())
+ private val utils = SceneTestUtils(this)
+ private val testScope = utils.testScope
lateinit var transitionController: LockscreenShadeTransitionController
lateinit var row: ExpandableNotificationRow
@@ -102,6 +102,8 @@
@Mock lateinit var qsTransitionController: LockscreenShadeQsTransitionController
@Mock lateinit var activityStarter: ActivityStarter
@Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
+ private val sceneContainerFlags = FakeSceneContainerFlags()
+ private val sceneInteractor = utils.sceneInteractor()
private val disableFlagsRepository = FakeDisableFlagsRepository()
private val keyguardRepository = FakeKeyguardRepository()
private val configurationRepository = FakeConfigurationRepository()
@@ -113,6 +115,8 @@
ShadeInteractor(
testScope.backgroundScope,
disableFlagsRepository,
+ sceneContainerFlags,
+ { sceneInteractor },
keyguardRepository,
userSetupRepository = FakeUserSetupRepository(),
deviceProvisionedController = mock(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 2e5afa4..98315d0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -18,6 +18,8 @@
import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_DENIED;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -439,11 +441,29 @@
public void setVoiceRegState(int voiceRegState) {
when(mServiceState.getState()).thenReturn(voiceRegState);
+ when(mServiceState.getVoiceRegState()).thenReturn(voiceRegState);
updateServiceState();
}
- public void setDataRegState(int dataRegState) {
- when(mServiceState.getDataRegistrationState()).thenReturn(dataRegState);
+ public void setDataRegInService(boolean inService) {
+ // mFakeRegInfo#isInService()
+ // Utils#isInService uses NetworkRegistrationInfo#isInService(). Since we can't
+ // mock the answer here, just set the bit based on what the caller wants
+ NetworkRegistrationInfo.Builder builder = new NetworkRegistrationInfo.Builder()
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setDomain(DOMAIN_PS)
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE);
+
+ if (inService) {
+ builder.setRegistrationState(REGISTRATION_STATE_HOME);
+ } else {
+ builder.setRegistrationState(REGISTRATION_STATE_DENIED);
+ }
+
+ NetworkRegistrationInfo fakeRegInfo = builder.build();
+ when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN))
+ .thenReturn(fakeRegInfo);
+
updateServiceState();
}
@@ -658,3 +678,4 @@
assertEquals("Data network name", expected, mNetworkController.getMobileDataNetworkName());
}
}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index d5689dc..f667b83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -340,7 +340,7 @@
public void testIsDataInService_notInService_false() {
setupDefaultSignal();
setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
- setDataRegState(ServiceState.STATE_OUT_OF_SERVICE);
+ setDataRegInService(false);
assertFalse(mNetworkController.isMobileDataNetworkInService());
}
@@ -408,3 +408,4 @@
false);
}
}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
new file mode 100644
index 0000000..e38adeb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager.NotifInflationErrorListener
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class NotifInflationErrorManagerTest : SysuiTestCase() {
+ private lateinit var manager: NotifInflationErrorManager
+
+ private val listener1 = mock(NotifInflationErrorListener::class.java)
+ private val listener2 = mock(NotifInflationErrorListener::class.java)
+
+ private val foo: NotificationEntry = NotificationEntryBuilder().setPkg("foo").build()
+ private val bar: NotificationEntry = NotificationEntryBuilder().setPkg("bar").build()
+ private val baz: NotificationEntry = NotificationEntryBuilder().setPkg("baz").build()
+
+ private val fooException = Exception("foo")
+ private val barException = Exception("bar")
+
+ @Before
+ fun setUp() {
+ // Reset manager instance before each test.
+ manager = NotifInflationErrorManager()
+ }
+
+ @Test
+ fun testTracksInflationErrors() {
+ manager.setInflationError(foo, fooException)
+ manager.setInflationError(bar, barException)
+
+ assertThat(manager.hasInflationError(foo)).isTrue()
+ assertThat(manager.hasInflationError(bar)).isTrue()
+ assertThat(manager.hasInflationError(baz)).isFalse()
+
+ manager.clearInflationError(bar)
+
+ assertThat(manager.hasInflationError(bar)).isFalse()
+ }
+
+ @Test
+ fun testNotifiesListeners() {
+ manager.addInflationErrorListener(listener1)
+ manager.setInflationError(foo, fooException)
+
+ verify(listener1).onNotifInflationError(foo, fooException)
+
+ manager.addInflationErrorListener(listener2)
+ manager.setInflationError(bar, barException)
+
+ verify(listener1).onNotifInflationError(bar, barException)
+ verify(listener2).onNotifInflationError(bar, barException)
+
+ manager.clearInflationError(foo)
+
+ verify(listener1).onNotifInflationErrorCleared(foo)
+ verify(listener2).onNotifInflationErrorCleared(foo)
+ }
+
+ @Test
+ fun testClearUnknownEntry() {
+ manager.addInflationErrorListener(listener1)
+ manager.clearInflationError(foo)
+
+ verify(listener1, never()).onNotifInflationErrorCleared(any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 6f431be..79cf932 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -628,6 +628,16 @@
verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
}
+ @Test
+ public void updateEmptyShadeView_onKeyguardOccludedTransitionToAod_hidesView() {
+ initController(/* viewIsAttached= */ true);
+ mController.onKeyguardTransitionChanged(
+ new TransitionStep(
+ /* from= */ KeyguardState.OCCLUDED,
+ /* to= */ KeyguardState.AOD));
+ verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
+ }
+
private LogMaker logMatcher(int category, int type) {
return argThat(new LogMatcher(category, type));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 75fb22d..521069f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -33,6 +33,8 @@
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
@@ -46,8 +48,6 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -59,12 +59,16 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class SharedNotificationContainerViewModelTest : SysuiTestCase() {
- private val testScope = TestScope(StandardTestDispatcher())
+ private val utils = SceneTestUtils(this)
+
+ private val testScope = utils.testScope
private val disableFlagsRepository = FakeDisableFlagsRepository()
private val userSetupRepository = FakeUserSetupRepository()
private val shadeRepository = FakeShadeRepository()
private val keyguardRepository = FakeKeyguardRepository()
+ private val sceneContainerFlags = FakeSceneContainerFlags()
+ private val sceneInteractor = utils.sceneInteractor()
private lateinit var configurationRepository: FakeConfigurationRepository
private lateinit var sharedNotificationContainerInteractor:
@@ -107,6 +111,8 @@
ShadeInteractor(
testScope.backgroundScope,
disableFlagsRepository,
+ sceneContainerFlags,
+ { sceneInteractor },
keyguardRepository,
userSetupRepository,
deviceProvisionedController,
@@ -220,6 +226,14 @@
)
)
assertThat(isOnLockscreen).isTrue()
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.FINISHED
+ )
+ )
+ assertThat(isOnLockscreen).isTrue()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 5107ecc..20e732a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -16,12 +16,9 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS;
import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
-
import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -133,7 +130,6 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mFeatureFlags = new FakeFeatureFlags();
- mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
@@ -439,24 +435,6 @@
@Test
public void onFPFailureNoHaptics_notInteractive_showLockScreen() {
- mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
-
- // GIVEN no vibrator and device is not interactive
- when(mVibratorHelper.hasVibrator()).thenReturn(false);
- when(mUpdateMonitor.isDeviceInteractive()).thenReturn(false);
- when(mUpdateMonitor.isDreaming()).thenReturn(false);
-
- // WHEN FP fails
- mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-
- // THEN wakeup the device
- verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
- }
-
- @Test
- public void onFPFailureNoHaptics_notInteractive_showLockScreen_doNotListenOccludingApps() {
- mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
-
// GIVEN no vibrator and device is not interactive
when(mVibratorHelper.hasVibrator()).thenReturn(false);
when(mUpdateMonitor.isDeviceInteractive()).thenReturn(false);
@@ -471,8 +449,6 @@
@Test
public void onFPFailureNoHaptics_dreaming_showLockScreen() {
- mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
-
// GIVEN no vibrator and device is dreaming
when(mVibratorHelper.hasVibrator()).thenReturn(false);
when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
@@ -486,22 +462,6 @@
}
@Test
- public void onFPFailureNoHaptics_dreaming_showLockScreen_doNotListeOccludingApps() {
- mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
-
- // GIVEN no vibrator and device is dreaming
- when(mVibratorHelper.hasVibrator()).thenReturn(false);
- when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
- when(mUpdateMonitor.isDreaming()).thenReturn(true);
-
- // WHEN FP fails
- mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-
- // THEN wakeup the device
- verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
- }
-
- @Test
public void onSideFingerprintSuccess_recentPowerButtonPress_noHaptic() {
// GIVEN side fingerprint enrolled, last wake reason was power button
when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 0da7360..798c3f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -18,7 +18,6 @@
import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
-
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -34,7 +33,6 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-
import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
import android.service.trust.TrustAgentService;
@@ -73,8 +71,9 @@
import com.android.systemui.bouncer.ui.BouncerViewDelegate;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -131,7 +130,7 @@
@Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
@Mock private DreamOverlayStateController mDreamOverlayStateController;
@Mock private LatencyTracker mLatencyTracker;
- @Mock private FeatureFlags mFeatureFlags;
+ private FakeFeatureFlags mFeatureFlags;
@Mock private KeyguardSecurityModel mKeyguardSecurityModel;
@Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
@Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@@ -171,9 +170,11 @@
.thenReturn(mKeyguardMessageAreaController);
when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback);
- when(mFeatureFlags
- .isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM))
- .thenReturn(true);
+ mFeatureFlags = new FakeFeatureFlags();
+ mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM, true);
+ mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false);
+ mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, true);
+ mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
when(mNotificationShadeWindowController.getWindowRootView())
.thenReturn(mNotificationShadeWindowView);
@@ -208,7 +209,8 @@
mActivityStarter,
mock(KeyguardTransitionInteractor.class),
StandardTestDispatcher(null, null),
- () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
+ () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
+ () -> mock(KeyguardDismissActionInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -711,7 +713,8 @@
mActivityStarter,
mock(KeyguardTransitionInteractor.class),
StandardTestDispatcher(null, null),
- () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
+ () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
+ () -> mock(KeyguardDismissActionInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 34c4ac1..233f407 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -15,7 +15,6 @@
package com.android.systemui.statusbar.phone;
import static android.view.Display.DEFAULT_DISPLAY;
-
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
@@ -54,8 +53,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -72,8 +71,8 @@
@RunWithLooper()
public class StatusBarNotificationPresenterTest extends SysuiTestCase {
private StatusBarNotificationPresenter mStatusBarNotificationPresenter;
- private final NotificationInterruptStateProvider mNotificationInterruptStateProvider =
- mock(NotificationInterruptStateProvider.class);
+ private final VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider =
+ mock(VisualInterruptionDecisionProvider.class);
private NotificationInterruptSuppressor mInterruptSuppressor;
private CommandQueue mCommandQueue;
private FakeMetricsLogger mMetricsLogger;
@@ -125,14 +124,14 @@
mock(NotificationMediaManager.class),
mock(NotificationGutsManager.class),
mInitController,
- mNotificationInterruptStateProvider,
+ mVisualInterruptionDecisionProvider,
mock(NotificationRemoteInputManager.class),
mock(NotificationRemoteInputManager.Callback.class),
mock(NotificationListContainer.class));
mInitController.executePostInitTasks();
ArgumentCaptor<NotificationInterruptSuppressor> suppressorCaptor =
ArgumentCaptor.forClass(NotificationInterruptSuppressor.class);
- verify(mNotificationInterruptStateProvider).addSuppressor(suppressorCaptor.capture());
+ verify(mVisualInterruptionDecisionProvider).addLegacySuppressor(suppressorCaptor.capture());
mInterruptSuppressor = suppressorCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 3af960b..8ef82c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -19,8 +19,13 @@
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
+import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN
+import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
import android.telephony.NetworkRegistrationInfo
+import android.telephony.NetworkRegistrationInfo.DOMAIN_PS
+import android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_DENIED
+import android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME
import android.telephony.ServiceState
import android.telephony.ServiceState.STATE_IN_SERVICE
import android.telephony.ServiceState.STATE_OUT_OF_SERVICE
@@ -80,7 +85,6 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -801,11 +805,18 @@
var latest: Boolean? = null
val job = underTest.isInService.onEach { latest = it }.launchIn(this)
+ val nriInService =
+ NetworkRegistrationInfo.Builder()
+ .setDomain(DOMAIN_PS)
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setRegistrationState(REGISTRATION_STATE_HOME)
+ .build()
+
getTelephonyCallbackForType<ServiceStateListener>()
.onServiceStateChanged(
ServiceState().also {
it.voiceRegState = STATE_IN_SERVICE
- it.dataRegState = STATE_IN_SERVICE
+ it.addNetworkRegistrationInfo(nriInService)
}
)
@@ -814,17 +825,23 @@
getTelephonyCallbackForType<ServiceStateListener>()
.onServiceStateChanged(
ServiceState().also {
- it.dataRegState = STATE_IN_SERVICE
it.voiceRegState = STATE_OUT_OF_SERVICE
+ it.addNetworkRegistrationInfo(nriInService)
}
)
assertThat(latest).isTrue()
+ val nriNotInService =
+ NetworkRegistrationInfo.Builder()
+ .setDomain(DOMAIN_PS)
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setRegistrationState(REGISTRATION_STATE_DENIED)
+ .build()
getTelephonyCallbackForType<ServiceStateListener>()
.onServiceStateChanged(
ServiceState().also {
it.voiceRegState = STATE_OUT_OF_SERVICE
- it.dataRegState = STATE_OUT_OF_SERVICE
+ it.addNetworkRegistrationInfo(nriNotInService)
}
)
assertThat(latest).isFalse()
@@ -838,18 +855,17 @@
var latest: Boolean? = null
val job = underTest.isInService.onEach { latest = it }.launchIn(this)
- // Mock the service state here so we can make it specifically IWLAN
- val serviceState: ServiceState = mock()
- whenever(serviceState.state).thenReturn(STATE_OUT_OF_SERVICE)
- whenever(serviceState.dataRegistrationState).thenReturn(STATE_IN_SERVICE)
-
- // See [com.android.settingslib.Utils.isInService] for more info. This is one way to
- // make the network look like IWLAN
- val networkRegWlan: NetworkRegistrationInfo = mock()
- whenever(serviceState.getNetworkRegistrationInfo(any(), any()))
- .thenReturn(networkRegWlan)
- whenever(networkRegWlan.registrationState)
- .thenReturn(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+ val iwlanData =
+ NetworkRegistrationInfo.Builder()
+ .setDomain(DOMAIN_PS)
+ .setTransportType(TRANSPORT_TYPE_WLAN)
+ .setRegistrationState(REGISTRATION_STATE_HOME)
+ .build()
+ val serviceState =
+ ServiceState().also {
+ it.voiceRegState = STATE_OUT_OF_SERVICE
+ it.addNetworkRegistrationInfo(iwlanData)
+ }
getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
assertThat(latest).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 243f881..e761635 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -46,8 +46,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bluetooth.BluetoothLogger;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
import com.android.systemui.statusbar.policy.bluetooth.FakeBluetoothRepository;
@@ -75,8 +73,6 @@
private DumpManager mMockDumpManager;
private BluetoothControllerImpl mBluetoothControllerImpl;
private BluetoothAdapter mMockAdapter;
- private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
-
private List<CachedBluetoothDevice> mDevices;
@Before
@@ -98,11 +94,9 @@
BluetoothRepository bluetoothRepository =
new FakeBluetoothRepository(mMockBluetoothManager);
- mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
mBluetoothControllerImpl = new BluetoothControllerImpl(
mContext,
- mFakeFeatureFlags,
mUserTracker,
mMockDumpManager,
mock(BluetoothLogger.class),
@@ -111,27 +105,8 @@
mMockBluetoothManager,
mMockAdapter);
}
-
@Test
- public void testNoConnectionWithDevices_repoFlagOff() {
- mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
-
- CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
- when(device.isConnected()).thenReturn(true);
- when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
- mDevices.add(device);
- when(mMockLocalAdapter.getConnectionState())
- .thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
-
- mBluetoothControllerImpl.onConnectionStateChanged(null,
- BluetoothAdapter.STATE_DISCONNECTED);
- assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
- }
-
- @Test
- public void testNoConnectionWithDevices_repoFlagOn() {
- mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
-
+ public void testNoConnectionWithDevices() {
CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
when(device.isConnected()).thenReturn(true);
when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
@@ -147,9 +122,7 @@
}
@Test
- public void testOnServiceConnected_updatesConnectionState_repoFlagOff() {
- mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
-
+ public void testOnServiceConnected_updatesConnectionState() {
when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
mBluetoothControllerImpl.onServiceConnected();
@@ -159,41 +132,7 @@
}
@Test
- public void testOnServiceConnected_updatesConnectionState_repoFlagOn() {
- mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
-
- when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
-
- mBluetoothControllerImpl.onServiceConnected();
-
- assertTrue(mBluetoothControllerImpl.isBluetoothConnecting());
- assertFalse(mBluetoothControllerImpl.isBluetoothConnected());
- }
-
- @Test
- public void getConnectedDevices_onlyReturnsConnected_repoFlagOff() {
- mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
-
- CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class);
- when(device1Disconnected.isConnected()).thenReturn(false);
- mDevices.add(device1Disconnected);
-
- CachedBluetoothDevice device2Connected = mock(CachedBluetoothDevice.class);
- when(device2Connected.isConnected()).thenReturn(true);
- mDevices.add(device2Connected);
-
- mBluetoothControllerImpl.onDeviceAdded(device1Disconnected);
- mBluetoothControllerImpl.onDeviceAdded(device2Connected);
-
- assertThat(mBluetoothControllerImpl.getConnectedDevices()).hasSize(1);
- assertThat(mBluetoothControllerImpl.getConnectedDevices().get(0))
- .isEqualTo(device2Connected);
- }
-
- @Test
- public void getConnectedDevices_onlyReturnsConnected_repoFlagOn() {
- mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
-
+ public void getConnectedDevices_onlyReturnsConnected() {
CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class);
when(device1Disconnected.isConnected()).thenReturn(false);
mDevices.add(device1Disconnected);
@@ -235,31 +174,7 @@
}
@Test
- public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOff() {
- mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
-
- BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
- mBluetoothControllerImpl.addCallback(callback);
-
- assertFalse(mBluetoothControllerImpl.isBluetoothConnected());
- CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
- mDevices.add(device);
- when(device.isConnected()).thenReturn(true);
- when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
- reset(callback);
- mBluetoothControllerImpl.onAclConnectionStateChanged(device,
- BluetoothProfile.STATE_CONNECTED);
-
- mTestableLooper.processAllMessages();
-
- assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
- verify(callback, atLeastOnce()).onBluetoothStateChange(anyBoolean());
- }
-
- @Test
- public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOn() {
- mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
-
+ public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection() {
BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
mBluetoothControllerImpl.addCallback(callback);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 1b623a3..7595a54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -134,6 +134,7 @@
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
+import com.android.wm.shell.bubbles.BubbleEducationController;
import com.android.wm.shell.bubbles.BubbleEntry;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubbleOverflow;
@@ -277,6 +278,8 @@
@Mock
private BubbleLogger mBubbleLogger;
@Mock
+ private BubbleEducationController mEducationController;
+ @Mock
private TaskStackListenerImpl mTaskStackListener;
@Mock
private KeyguardStateController mKeyguardStateController;
@@ -369,7 +372,8 @@
mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
mPositioner.setMaxBubbles(5);
- mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
+ mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, mEducationController,
+ syncExecutor);
when(mUserManager.getProfiles(ActivityManager.getCurrentUser())).thenReturn(
Collections.singletonList(mock(UserInfo.class)));
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index 2362a52..0c5e438 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -20,16 +20,12 @@
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeFingerprintPropertyRepository : FingerprintPropertyRepository {
- private val _isInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val isInitialized = _isInitialized.asStateFlow()
-
private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
- override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
+ override val sensorId = _sensorId.asStateFlow()
private val _strength: MutableStateFlow<SensorStrength> =
MutableStateFlow(SensorStrength.CONVENIENCE)
@@ -37,12 +33,11 @@
private val _sensorType: MutableStateFlow<FingerprintSensorType> =
MutableStateFlow(FingerprintSensorType.UNKNOWN)
- override val sensorType: StateFlow<FingerprintSensorType> = _sensorType.asStateFlow()
+ override val sensorType = _sensorType.asStateFlow()
private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
- override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
- _sensorLocations.asStateFlow()
+ override val sensorLocations = _sensorLocations.asStateFlow()
fun setProperties(
sensorId: Int,
@@ -54,6 +49,5 @@
_strength.value = strength
_sensorType.value = sensorType
_sensorLocations.value = sensorLocations
- _isInitialized.value = true
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
index 0847c85..b45c198 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
@@ -2,11 +2,14 @@
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
-/** Fake implementation of [KeyguardRepository] */
+/** Fake implementation of [KeyguardBouncerRepository] */
class FakeKeyguardBouncerRepository : KeyguardBouncerRepository {
private val _primaryBouncerShow = MutableStateFlow(false)
override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
@@ -26,7 +29,13 @@
private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
- override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+ override val keyguardAuthenticatedBiometrics = _keyguardAuthenticated.asStateFlow()
+ private val _keyguardAuthenticatedPrimaryAuth = MutableSharedFlow<Int>()
+ override val keyguardAuthenticatedPrimaryAuth: Flow<Int> =
+ _keyguardAuthenticatedPrimaryAuth.asSharedFlow()
+ private val _userRequestedBouncerWhenAlreadyAuthenticated = MutableSharedFlow<Int>()
+ override val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> =
+ _userRequestedBouncerWhenAlreadyAuthenticated.asSharedFlow()
private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
override val showMessage = _showMessage.asStateFlow()
private val _resourceUpdateRequests = MutableStateFlow(false)
@@ -83,10 +92,18 @@
_showMessage.value = bouncerShowMessageModel
}
- override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+ override fun setKeyguardAuthenticatedBiometrics(keyguardAuthenticated: Boolean?) {
_keyguardAuthenticated.value = keyguardAuthenticated
}
+ override suspend fun setKeyguardAuthenticatedPrimaryAuth(userId: Int) {
+ _keyguardAuthenticatedPrimaryAuth.emit(userId)
+ }
+
+ override suspend fun setUserRequestedBouncerWhenAlreadyAuthenticated(userId: Int) {
+ _userRequestedBouncerWhenAlreadyAuthenticated.emit(userId)
+ }
+
override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
_isBackButtonEnabled.value = isBackButtonEnabled
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 2ac625d..5cd09d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -22,11 +22,19 @@
import org.mockito.Mockito.`when` as whenever
/** Creates a mock display. */
-fun display(type: Int, flags: Int = 0, id: Int = 0): Display {
+fun display(
+ type: Int,
+ flags: Int = 0,
+ id: Int = 0,
+ state: Int? = null,
+): Display {
return mock {
whenever(this.displayId).thenReturn(id)
whenever(this.type).thenReturn(type)
whenever(this.flags).thenReturn(flags)
+ if (state != null) {
+ whenever(this.state).thenReturn(state)
+ }
}
}
@@ -35,7 +43,7 @@
mock<DisplayRepository.PendingDisplay> { whenever(this.id).thenReturn(id) }
/** Fake [DisplayRepository] implementation for testing. */
-class FakeDisplayRepository : DisplayRepository {
+class FakeDisplayRepository() : DisplayRepository {
private val flow = MutableSharedFlow<Set<Display>>()
private val pendingDisplayFlow = MutableSharedFlow<DisplayRepository.PendingDisplay?>()
@@ -50,4 +58,8 @@
override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
get() = pendingDisplayFlow
+
+ private val _displayChangeEvent = MutableSharedFlow<Int>()
+ override val displayChangeEvent: Flow<Int> = _displayChangeEvent
+ suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index cc0c943..dae8644 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -21,7 +21,9 @@
import com.android.systemui.common.shared.model.Position
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
import com.android.systemui.keyguard.shared.model.ScreenModel
import com.android.systemui.keyguard.shared.model.ScreenState
@@ -30,12 +32,18 @@
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/** Fake implementation of [KeyguardRepository] */
class FakeKeyguardRepository : KeyguardRepository {
+ private val _deferKeyguardDone: MutableSharedFlow<KeyguardDone> = MutableSharedFlow()
+ override val keyguardDone: Flow<KeyguardDone> = _deferKeyguardDone
+
+ private val _dismissAction = MutableStateFlow<DismissAction>(DismissAction.None)
+ override val dismissAction: StateFlow<DismissAction> = _dismissAction
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions: StateFlow<Boolean> =
@@ -175,6 +183,14 @@
_dozeTimeTick.value = _dozeTimeTick.value + 1
}
+ override fun setDismissAction(dismissAction: DismissAction) {
+ _dismissAction.value = dismissAction
+ }
+
+ override suspend fun setKeyguardDone(timing: KeyguardDone) {
+ _deferKeyguardDone.emit(timing)
+ }
+
fun dozeTimeTick(millis: Long) {
_dozeTimeTick.value = millis
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
index 817e1db..9d98f94 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
@@ -17,6 +17,8 @@
package com.android.systemui.keyguard.data.repository
+import com.android.keyguard.TrustGrantFlags
+import com.android.systemui.keyguard.shared.model.TrustModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -35,6 +37,9 @@
override val isCurrentUserTrustManaged: StateFlow<Boolean>
get() = _isCurrentUserTrustManaged
+ private val _requestDismissKeyguard = MutableStateFlow(TrustModel(false, 0, TrustGrantFlags(0)))
+ override val trustAgentRequestingToDismissKeyguard: Flow<TrustModel> = _requestDismissKeyguard
+
fun setCurrentUserTrusted(trust: Boolean) {
_isCurrentUserTrusted.value = trust
}
@@ -46,4 +51,8 @@
fun setCurrentUserActiveUnlockAvailable(available: Boolean) {
_isCurrentUserActiveUnlockAvailable.value = available
}
+
+ fun setRequestDismissKeyguard(trustModel: TrustModel) {
+ _requestDismissKeyguard.value = trustModel
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
new file mode 100644
index 0000000..6dd41f4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.app.ActivityManager
+import android.content.Context
+import android.os.Handler
+import android.os.UserManager
+import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.ui.BouncerView
+import com.android.systemui.broadcast.FakeBroadcastDispatcher
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
+import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.time.FakeSystemClock
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.test.TestScope
+import org.mockito.Mockito.mock
+
+/**
+ * Helper to create a new KeyguardDismissInteractor in a way that doesn't require modifying many
+ * tests whenever we add a constructor param.
+ */
+object KeyguardDismissInteractorFactory {
+ @JvmOverloads
+ @JvmStatic
+ fun create(
+ context: Context,
+ testScope: TestScope,
+ broadcastDispatcher: FakeBroadcastDispatcher,
+ dispatcher: CoroutineDispatcher,
+ trustRepository: FakeTrustRepository = FakeTrustRepository(),
+ keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
+ bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
+ keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(KeyguardUpdateMonitor::class.java),
+ featureFlags: FakeFeatureFlagsClassic =
+ FakeFeatureFlagsClassic().apply {
+ set(Flags.DELAY_BOUNCER, true)
+ set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, true)
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ },
+ powerRepository: FakePowerRepository = FakePowerRepository(),
+ userRepository: FakeUserRepository = FakeUserRepository(),
+ ): WithDependencies {
+ val primaryBouncerInteractor =
+ PrimaryBouncerInteractor(
+ bouncerRepository,
+ mock(BouncerView::class.java),
+ mock(Handler::class.java),
+ mock(KeyguardStateController::class.java),
+ mock(KeyguardSecurityModel::class.java),
+ mock(PrimaryBouncerCallbackInteractor::class.java),
+ mock(FalsingCollector::class.java),
+ mock(DismissCallbackRegistry::class.java),
+ context,
+ keyguardUpdateMonitor,
+ trustRepository,
+ featureFlags,
+ testScope.backgroundScope,
+ )
+ val alternateBouncerInteractor =
+ AlternateBouncerInteractor(
+ mock(StatusBarStateController::class.java),
+ mock(KeyguardStateController::class.java),
+ bouncerRepository,
+ FakeBiometricSettingsRepository(),
+ FakeSystemClock(),
+ keyguardUpdateMonitor,
+ )
+ val powerInteractor =
+ PowerInteractor(
+ powerRepository,
+ keyguardRepository,
+ mock(FalsingCollector::class.java),
+ mock(ScreenOffAnimationController::class.java),
+ mock(StatusBarStateController::class.java),
+ )
+ val userInteractor =
+ UserInteractor(
+ applicationContext = context,
+ repository = userRepository,
+ mock(ActivityStarter::class.java),
+ keyguardInteractor =
+ KeyguardInteractorFactory.create(
+ repository = keyguardRepository,
+ bouncerRepository = bouncerRepository,
+ featureFlags = featureFlags,
+ )
+ .keyguardInteractor,
+ featureFlags = featureFlags,
+ manager = mock(UserManager::class.java),
+ headlessSystemUserMode = mock(HeadlessSystemUserMode::class.java),
+ applicationScope = testScope.backgroundScope,
+ telephonyInteractor =
+ TelephonyInteractor(
+ repository = FakeTelephonyRepository(),
+ ),
+ broadcastDispatcher = broadcastDispatcher,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ backgroundDispatcher = dispatcher,
+ activityManager = mock(ActivityManager::class.java),
+ refreshUsersScheduler = mock(RefreshUsersScheduler::class.java),
+ guestUserInteractor = mock(GuestUserInteractor::class.java),
+ uiEventLogger = mock(UiEventLogger::class.java),
+ )
+ return WithDependencies(
+ trustRepository = trustRepository,
+ keyguardRepository = keyguardRepository,
+ bouncerRepository = bouncerRepository,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ powerRepository = powerRepository,
+ userRepository = userRepository,
+ interactor =
+ KeyguardDismissInteractor(
+ trustRepository,
+ keyguardRepository,
+ primaryBouncerInteractor,
+ alternateBouncerInteractor,
+ powerInteractor,
+ userInteractor,
+ ),
+ )
+ }
+
+ data class WithDependencies(
+ val trustRepository: FakeTrustRepository,
+ val keyguardRepository: FakeKeyguardRepository,
+ val bouncerRepository: FakeKeyguardBouncerRepository,
+ val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ val powerRepository: FakePowerRepository,
+ val userRepository: FakeUserRepository,
+ val interactor: KeyguardDismissInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
index 2865710..aa8dbe1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
@@ -16,7 +16,6 @@
package com.android.systemui.qs.pipeline.data.repository
-import android.util.Log
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.shared.TileSpec
import kotlinx.coroutines.flow.Flow
@@ -28,7 +27,7 @@
private val tilesPerUser = mutableMapOf<Int, MutableStateFlow<List<TileSpec>>>()
override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
- return getFlow(userId).asStateFlow().also { Log.d("Fabian", "Retrieving flow for $userId") }
+ return getFlow(userId).asStateFlow()
}
override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 9dea0a0..2d79e0f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -127,6 +127,7 @@
)
}
+ @JvmOverloads
fun sceneInteractor(
repository: SceneContainerRepository = fakeSceneContainerRepository()
): SceneInteractor {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
index 39756df..cae047f 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
@@ -18,7 +18,6 @@
import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
import static android.service.autofill.AutofillService.EXTRA_RESULT;
-
import static com.android.server.autofill.AutofillManagerService.RECEIVER_BUNDLE_EXTRA_SESSIONS;
import android.os.Bundle;
@@ -155,20 +154,31 @@
pw.println("");
}
+ Method[] flagMethods = {};
+
try {
- Method[] flagMethods = Flags.class.getMethods();
- // For some reason, unreferenced flags do not show up here
- // Maybe compiler optomized them out of bytecode?
- for (Method method : flagMethods) {
- if (Modifier.isPublic(method.getModifiers())) {
- pw.println(method.getName() + ": " + method.invoke(null));
- }
- }
- } catch (Exception ex) {
- pw.println(ex);
+ flagMethods = Flags.class.getDeclaredMethods();
+ } catch (SecurityException ex) {
+ ex.printStackTrace(pw);
return -1;
}
+ // For some reason, unreferenced flags do not show up here
+ // Maybe compiler optomized them out of bytecode?
+ for (Method method : flagMethods) {
+ if (!Modifier.isPublic(method.getModifiers())) {
+ continue;
+ }
+ try {
+ pw.print(method.getName() + ": ");
+ pw.print(method.invoke(null));
+ } catch (Exception ex) {
+ ex.printStackTrace(pw);
+ } finally {
+ pw.println("");
+ }
+ }
+
return 0;
}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index b07a0bb..102c262 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -47,7 +47,6 @@
import java.util.Set;
-
/**
* A controller to control the policies of the windows that can be displayed on the virtual display.
*/
@@ -106,12 +105,14 @@
public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L;
@NonNull
private final ArraySet<UserHandle> mAllowedUsers;
- private final boolean mActivityLaunchAllowedByDefault;
+ @GuardedBy("mGenericWindowPolicyControllerLock")
+ private boolean mActivityLaunchAllowedByDefault;
@NonNull
- private final ArraySet<ComponentName> mActivityPolicyExceptions;
+ @GuardedBy("mGenericWindowPolicyControllerLock")
+ private final Set<ComponentName> mActivityPolicyExemptions;
private final boolean mCrossTaskNavigationAllowedByDefault;
@NonNull
- private final ArraySet<ComponentName> mCrossTaskNavigationExceptions;
+ private final ArraySet<ComponentName> mCrossTaskNavigationExemptions;
private final Object mGenericWindowPolicyControllerLock = new Object();
@Nullable private final ActivityBlockedCallback mActivityBlockedCallback;
private int mDisplayId = Display.INVALID_DISPLAY;
@@ -142,11 +143,11 @@
* @param allowedUsers The set of users that are allowed to stream in this display.
* @param activityLaunchAllowedByDefault Whether activities are default allowed to be launched
* or blocked.
- * @param activityPolicyExceptions The set of activities explicitly exempt from the default
+ * @param activityPolicyExemptions The set of activities explicitly exempt from the default
* activity policy.
* @param crossTaskNavigationAllowedByDefault Whether cross task navigations are allowed by
* default or not.
- * @param crossTaskNavigationExceptions The set of components explicitly exempt from the default
+ * @param crossTaskNavigationExemptions The set of components explicitly exempt from the default
* navigation policy.
* @param activityListener Activity listener to listen for activity changes.
* @param activityBlockedCallback Callback that is called when an activity is blocked from
@@ -157,12 +158,14 @@
* passed in filters.
* @param showTasksInHostDeviceRecents whether to show activities in recents on the host device.
*/
- public GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
+ public GenericWindowPolicyController(
+ int windowFlags,
+ int systemWindowFlags,
@NonNull ArraySet<UserHandle> allowedUsers,
boolean activityLaunchAllowedByDefault,
- @NonNull Set<ComponentName> activityPolicyExceptions,
+ @NonNull Set<ComponentName> activityPolicyExemptions,
boolean crossTaskNavigationAllowedByDefault,
- @NonNull Set<ComponentName> crossTaskNavigationExceptions,
+ @NonNull Set<ComponentName> crossTaskNavigationExemptions,
@Nullable ActivityListener activityListener,
@Nullable PipBlockedCallback pipBlockedCallback,
@Nullable ActivityBlockedCallback activityBlockedCallback,
@@ -173,9 +176,9 @@
super();
mAllowedUsers = allowedUsers;
mActivityLaunchAllowedByDefault = activityLaunchAllowedByDefault;
- mActivityPolicyExceptions = new ArraySet<>(activityPolicyExceptions);
+ mActivityPolicyExemptions = activityPolicyExemptions;
mCrossTaskNavigationAllowedByDefault = crossTaskNavigationAllowedByDefault;
- mCrossTaskNavigationExceptions = new ArraySet<>(crossTaskNavigationExceptions);
+ mCrossTaskNavigationExemptions = new ArraySet<>(crossTaskNavigationExemptions);
mActivityBlockedCallback = activityBlockedCallback;
setInterestedWindowFlags(windowFlags, systemWindowFlags);
mActivityListener = activityListener;
@@ -202,6 +205,24 @@
}
}
+ void setActivityLaunchDefaultAllowed(boolean activityLaunchDefaultAllowed) {
+ synchronized (mGenericWindowPolicyControllerLock) {
+ mActivityLaunchAllowedByDefault = activityLaunchDefaultAllowed;
+ }
+ }
+
+ void addActivityPolicyExemption(@NonNull ComponentName componentName) {
+ synchronized (mGenericWindowPolicyControllerLock) {
+ mActivityPolicyExemptions.add(componentName);
+ }
+ }
+
+ void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
+ synchronized (mGenericWindowPolicyControllerLock) {
+ mActivityPolicyExemptions.remove(componentName);
+ }
+ }
+
/** Register a listener for running applications changes. */
public void registerRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
synchronized (mGenericWindowPolicyControllerLock) {
@@ -265,14 +286,17 @@
+ mDisplayCategories);
return false;
}
- if (!isAllowedByPolicy(mActivityLaunchAllowedByDefault, mActivityPolicyExceptions,
- activityComponent)) {
- Slog.d(TAG, "Virtual device launch disallowed by policy: " + activityComponent);
- return false;
+ synchronized (mGenericWindowPolicyControllerLock) {
+ if (!isAllowedByPolicy(mActivityLaunchAllowedByDefault, mActivityPolicyExemptions,
+ activityComponent)) {
+ Slog.d(TAG, "Virtual device launch disallowed by policy: "
+ + activityComponent);
+ return false;
+ }
}
if (isNewTask && launchingFromDisplayId != DEFAULT_DISPLAY
&& !isAllowedByPolicy(mCrossTaskNavigationAllowedByDefault,
- mCrossTaskNavigationExceptions, activityComponent)) {
+ mCrossTaskNavigationExemptions, activityComponent)) {
Slog.d(TAG, "Virtual device cross task navigation disallowed by policy: "
+ activityComponent);
return false;
@@ -378,11 +402,11 @@
&& mDisplayCategories.contains(activityInfo.requiredDisplayCategory);
}
- private boolean isAllowedByPolicy(boolean allowedByDefault, ArraySet<ComponentName> exceptions,
- ComponentName component) {
- // Either allowed and the exceptions do not contain the component,
- // or disallowed and the exceptions contain the component.
- return allowedByDefault != exceptions.contains(component);
+ private static boolean isAllowedByPolicy(boolean allowedByDefault,
+ Set<ComponentName> exemptions, ComponentName component) {
+ // Either allowed and the exemptions do not contain the component,
+ // or disallowed and the exemptions contain the component.
+ return allowedByDefault != exemptions.contains(component);
}
@VisibleForTesting
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 8f765e4..3b13410 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -22,6 +22,7 @@
import static android.companion.virtual.VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -93,7 +94,6 @@
import android.view.WindowManager;
import android.widget.Toast;
-
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.BlockedAppStreamingActivity;
@@ -174,11 +174,15 @@
@NonNull
private final VirtualDevice mPublicVirtualDeviceObject;
+ @GuardedBy("mVirtualDeviceLock")
+ @NonNull
+ private final Set<ComponentName> mActivityPolicyExemptions;
+
private ActivityListener createListenerAdapter() {
return new ActivityListener() {
@Override
- public void onTopActivityChanged(int displayId, ComponentName topActivity) {
+ public void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity) {
try {
mActivityListener.onTopActivityChanged(displayId, topActivity,
UserHandle.USER_NULL);
@@ -188,7 +192,7 @@
}
@Override
- public void onTopActivityChanged(int displayId, ComponentName topActivity,
+ public void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity,
@UserIdInt int userId) {
try {
mActivityListener.onTopActivityChanged(displayId, topActivity, userId);
@@ -295,6 +299,18 @@
mPublicVirtualDeviceObject = new VirtualDevice(
this, getDeviceId(), getPersistentDeviceId(), mParams.getName());
+
+ if (Flags.dynamicPolicy()) {
+ mActivityPolicyExemptions = new ArraySet<>(
+ mParams.getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT
+ ? mParams.getBlockedActivities()
+ : mParams.getAllowedActivities());
+ } else {
+ mActivityPolicyExemptions =
+ mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED
+ ? mParams.getBlockedActivities()
+ : mParams.getAllowedActivities();
+ }
}
@VisibleForTesting
@@ -414,6 +430,34 @@
}
}
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void addActivityPolicyExemption(@NonNull ComponentName componentName) {
+ super.addActivityPolicyExemption_enforcePermission();
+ synchronized (mVirtualDeviceLock) {
+ if (mActivityPolicyExemptions.add(componentName)) {
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ mVirtualDisplays.valueAt(i).getWindowPolicyController()
+ .addActivityPolicyExemption(componentName);
+ }
+ }
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
+ super.removeActivityPolicyExemption_enforcePermission();
+ synchronized (mVirtualDeviceLock) {
+ if (mActivityPolicyExemptions.remove(componentName)) {
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ mVirtualDisplays.valueAt(i).getWindowPolicyController()
+ .removeActivityPolicyExemption(componentName);
+ }
+ }
+ }
+ }
+
private void sendPendingIntent(int displayId, PendingIntent pendingIntent)
throws PendingIntent.CanceledException {
final ActivityOptions options = ActivityOptions.makeBasic().setLaunchDisplayId(displayId);
@@ -543,6 +587,16 @@
}
}
break;
+ case POLICY_TYPE_ACTIVITY:
+ synchronized (mVirtualDeviceLock) {
+ mDevicePolicies.put(policyType, devicePolicy);
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ mVirtualDisplays.valueAt(i).getWindowPolicyController()
+ .setActivityLaunchDefaultAllowed(
+ devicePolicy == DEVICE_POLICY_DEFAULT);
+ }
+ }
+ break;
default:
throw new IllegalArgumentException("Device policy " + policyType
+ " cannot be changed at runtime. ");
@@ -840,24 +894,26 @@
mSensorController.dump(fout);
}
- private GenericWindowPolicyController createWindowPolicyController(
+ @GuardedBy("mVirtualDeviceLock")
+ private GenericWindowPolicyController createWindowPolicyControllerLocked(
@NonNull Set<String> displayCategories) {
final boolean activityLaunchAllowedByDefault =
- mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED;
+ Flags.dynamicPolicy()
+ ? getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT
+ : mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED;
final boolean crossTaskNavigationAllowedByDefault =
mParams.getDefaultNavigationPolicy() == NAVIGATION_POLICY_DEFAULT_ALLOWED;
final boolean showTasksInHostDeviceRecents =
- mParams.getDevicePolicy(POLICY_TYPE_RECENTS) == DEVICE_POLICY_DEFAULT;
+ getDevicePolicy(POLICY_TYPE_RECENTS) == DEVICE_POLICY_DEFAULT;
final GenericWindowPolicyController gwpc = new GenericWindowPolicyController(
FLAG_SECURE,
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
getAllowedUserHandles(),
activityLaunchAllowedByDefault,
- /*activityPolicyExceptions=*/activityLaunchAllowedByDefault
- ? mParams.getBlockedActivities() : mParams.getAllowedActivities(),
+ mActivityPolicyExemptions,
crossTaskNavigationAllowedByDefault,
- /*crossTaskNavigationExceptions=*/crossTaskNavigationAllowedByDefault
+ /*crossTaskNavigationExemptions=*/crossTaskNavigationAllowedByDefault
? mParams.getBlockedCrossTaskNavigations()
: mParams.getAllowedCrossTaskNavigations(),
createListenerAdapter(),
@@ -873,8 +929,10 @@
int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
@NonNull IVirtualDisplayCallback callback, String packageName) {
- GenericWindowPolicyController gwpc = createWindowPolicyController(
- virtualDisplayConfig.getDisplayCategories());
+ GenericWindowPolicyController gwpc;
+ synchronized (mVirtualDeviceLock) {
+ gwpc = createWindowPolicyControllerLocked(virtualDisplayConfig.getDisplayCategories());
+ }
DisplayManagerInternal displayManager = LocalServices.getService(
DisplayManagerInternal.class);
int displayId;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index b941aaf..d9c2694 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -192,8 +192,8 @@
"apache-commons-math",
"power_optimization_flags_lib",
"notification_flags_lib",
- "pm_flags_lib",
"camera_platform_flags_core_java_lib",
+ "biometrics_flags_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 87077a6..a451f36 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -159,7 +159,6 @@
import com.android.internal.annotations.CompositeRWLock;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.am.PlatformCompatCache.CachedCompatChangeId;
import com.android.server.wm.ActivityServiceConnectionsHolder;
@@ -317,11 +316,6 @@
static final long USE_SHORT_FGS_USAGE_INTERACTION_TIME = 183972877L;
/**
- * For some direct access we need to power manager.
- */
- PowerManagerInternal mLocalPowerManager;
-
- /**
* Service for optimizing resource usage from background apps.
*/
CachedAppOptimizer mCachedAppOptimizer;
@@ -431,7 +425,6 @@
mProcLock = service.mProcLock;
mActiveUids = activeUids;
- mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
mConstants = mService.mConstants;
mCachedAppOptimizer = new CachedAppOptimizer(mService);
mCacheOomRanker = new CacheOomRanker(service);
@@ -1480,8 +1473,8 @@
becameIdle.clear();
// Update from any uid changes.
- if (mLocalPowerManager != null) {
- mLocalPowerManager.startUidChanges();
+ if (mService.mLocalPowerManager != null) {
+ mService.mLocalPowerManager.startUidChanges();
}
for (int i = activeUids.size() - 1; i >= 0; i--) {
final UidRecord uidRec = activeUids.valueAt(i);
@@ -1575,8 +1568,8 @@
}
mService.mInternal.deletePendingTopUid(uidRec.getUid(), nowElapsed);
}
- if (mLocalPowerManager != null) {
- mLocalPowerManager.finishUidChanges();
+ if (mService.mLocalPowerManager != null) {
+ mService.mLocalPowerManager.finishUidChanges();
}
int size = becameIdle.size();
@@ -3613,8 +3606,8 @@
final long nowElapsed = SystemClock.elapsedRealtime();
final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME;
long nextTime = 0;
- if (mLocalPowerManager != null) {
- mLocalPowerManager.startUidChanges();
+ if (mService.mLocalPowerManager != null) {
+ mService.mLocalPowerManager.startUidChanges();
}
for (int i = N - 1; i >= 0; i--) {
final UidRecord uidRec = mActiveUids.valueAt(i);
@@ -3634,8 +3627,8 @@
}
}
}
- if (mLocalPowerManager != null) {
- mLocalPowerManager.finishUidChanges();
+ if (mService.mLocalPowerManager != null) {
+ mService.mLocalPowerManager.finishUidChanges();
}
// Also check if there are any apps in cached and background restricted mode,
// if so, kill it if it's been there long enough, or kick off a msg to check
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index b79ba7ba..10a1b9e 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -115,28 +115,38 @@
};
// All the aconfig flags under the listed DeviceConfig scopes will be synced to native level.
+ // The list is sorted.
@VisibleForTesting
static final String[] sDeviceConfigAconfigScopes = new String[] {
- "biometrics_framework",
- "core_experiments_team_internal",
- "camera_platform",
- "power",
- "vibrator",
- "haptics",
- "text",
+ "android_core_networking",
"arc_next",
- "test_suites",
- "hardware_backed_security_mainline",
- "threadnetwork",
- "media_solutions",
- "responsible_apis",
- "rust",
- "pixel_biometrics",
+ "biometrics_framework",
+ "biometrics_integration",
+ "camera_platform",
+ "car_framework",
"car_perception",
"car_security",
"car_telemetry",
- "car_framework",
- "android-nfc",
+ "codec_fwk",
+ "core_experiments_team_internal",
+ "haptics",
+ "hardware_backed_security_mainline",
+ "media_solutions",
+ "nfc",
+ "pixel_system_sw_touch",
+ "pixel_watch",
+ "power",
+ "responsible_apis",
+ "rust",
+ "test_suites",
+ "text",
+ "threadnetwork",
+ "vibrator",
+ "wear_frameworks",
+ "wear_system_health",
+ "wear_systems",
+ "window_surfaces",
+ "windowing_frontend"
};
private final String[] mGlobalSettings;
diff --git a/services/core/java/com/android/server/biometrics/Android.bp b/services/core/java/com/android/server/biometrics/Android.bp
new file mode 100644
index 0000000..6cbe4ad
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+ name: "biometrics_flags",
+ package: "com.android.server.biometrics",
+ srcs: [
+ "biometrics.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "biometrics_flags_lib",
+ aconfig_declarations: "biometrics_flags",
+}
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
new file mode 100644
index 0000000..b537e0e
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.biometrics"
+
+flag {
+ name: "face_vhal_feature"
+ namespace: "biometrics_framework"
+ description: "This flag controls tunscany virtual HAL feature"
+ bug: "294254230"
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 6f26e7b..5084b60 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -58,6 +58,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -67,6 +68,8 @@
import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
import com.android.server.biometrics.sensors.face.hidl.Face10;
+import com.google.android.collect.Lists;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -639,15 +642,24 @@
provider.second.scheduleGetFeature(provider.first, token, userId, feature,
new ClientMonitorCallbackConverter(receiver), opPackageName);
}
-
- private List<ServiceProvider> getAidlProviders() {
+ @NonNull
+ private List<ServiceProvider> getHidlProviders(
+ @NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
final List<ServiceProvider> providers = new ArrayList<>();
- final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
- if (instances == null || instances.length == 0) {
- return providers;
+ for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
+ providers.add(
+ Face10.newInstance(getContext(), mBiometricStateCallback,
+ hidlSensor, mLockoutResetDispatcher));
}
+ return providers;
+ }
+
+ @NonNull
+ private List<ServiceProvider> getAidlProviders(@NonNull List<String> instances) {
+ final List<ServiceProvider> providers = new ArrayList<>();
+
for (String instance : instances) {
final String fqName = IFace.DESCRIPTOR + "/" + instance;
final IFace face = IFace.Stub.asInterface(
@@ -676,17 +688,55 @@
super.registerAuthenticators_enforcePermission();
mRegistry.registerAll(() -> {
- final List<ServiceProvider> providers = new ArrayList<>();
- for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
- providers.add(
- Face10.newInstance(getContext(), mBiometricStateCallback,
- hidlSensor, mLockoutResetDispatcher));
+ List<String> aidlSensors = new ArrayList<>();
+ final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+ if (instances != null) {
+ aidlSensors.addAll(Lists.newArrayList(instances));
}
- providers.addAll(getAidlProviders());
+
+ final Pair<List<FaceSensorPropertiesInternal>, List<String>>
+ filteredInstances = filterAvailableHalInstances(hidlSensors, aidlSensors);
+
+ final List<ServiceProvider> providers = new ArrayList<>();
+ providers.addAll(getHidlProviders(filteredInstances.first));
+ providers.addAll(getAidlProviders(filteredInstances.second));
return providers;
});
}
+ private Pair<List<FaceSensorPropertiesInternal>, List<String>>
+ filterAvailableHalInstances(
+ @NonNull List<FaceSensorPropertiesInternal> hidlInstances,
+ @NonNull List<String> aidlInstances) {
+ if ((hidlInstances.size() + aidlInstances.size()) <= 1) {
+ return new Pair(hidlInstances, aidlInstances);
+ }
+
+ if (Flags.faceVhalFeature()) {
+ Slog.i(TAG, "Face VHAL feature is on");
+ } else {
+ Slog.i(TAG, "Face VHAL feature is off");
+ }
+
+ final int virtualAt = aidlInstances.indexOf("virtual");
+ if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
+ if (virtualAt != -1) {
+ //only virtual instance should be returned
+ return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt)));
+ } else {
+ Slog.e(TAG, "Could not find virtual interface while it is enabled");
+ return new Pair(hidlInstances, aidlInstances);
+ }
+ } else {
+ //remove virtual instance
+ aidlInstances = new ArrayList<>(aidlInstances);
+ if (virtualAt != -1) {
+ aidlInstances.remove(virtualAt);
+ }
+ return new Pair(hidlInstances, aidlInstances);
+ }
+ }
+
@Override
public void addAuthenticatorsRegisteredCallback(
IFaceAuthenticatorsRegisteredCallback callback) {
@@ -749,7 +799,7 @@
void syncEnrollmentsNow() {
Utils.checkPermissionOrShell(getContext(), MANAGE_FACE);
- if (Utils.isVirtualEnabled(getContext())) {
+ if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
Slog.i(TAG, "Sync virtual enrollments");
final int userId = ActivityManager.getCurrentUser();
for (ServiceProvider provider : mRegistry.getProviders()) {
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index b890bbd..9805fd3 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -1754,7 +1754,7 @@
eventType = parser.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
}
- } catch (XmlPullParserException e) {
+ } catch (XmlPullParserException | ArrayIndexOutOfBoundsException e) {
Slog.w(TAG, "Error reading accounts", e);
return;
} catch (java.io.IOException e) {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 4d8c02b..2c3c66e 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -7,4 +7,5 @@
namespace: "display_manager"
description: "Feature flag for Connected Display managment"
bug: "280739508"
+ is_fixed_read_only: true
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 20c7029..2fe2271 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -70,7 +70,7 @@
@NonNull private final WindowManagerInternal mWindowManagerInternal;
@GuardedBy("ImfLock.class") private long mLastBindTime;
- @GuardedBy("ImfLock.class") private boolean mHasConnection;
+ @GuardedBy("ImfLock.class") private boolean mHasMainConnection;
@GuardedBy("ImfLock.class") @Nullable private String mCurId;
@GuardedBy("ImfLock.class") @Nullable private String mSelectedMethodId;
@GuardedBy("ImfLock.class") @Nullable private Intent mCurIntent;
@@ -137,8 +137,8 @@
* a service (whether or not we have gotten its IBinder back yet).
*/
@GuardedBy("ImfLock.class")
- boolean hasConnection() {
- return mHasConnection;
+ boolean hasMainConnection() {
+ return mHasMainConnection;
}
/**
@@ -369,7 +369,7 @@
unbindVisibleConnection();
}
- if (hasConnection()) {
+ if (hasMainConnection()) {
unbindMainConnection();
}
@@ -464,7 +464,7 @@
@GuardedBy("ImfLock.class")
private void unbindMainConnection() {
mContext.unbindService(mMainConnection);
- mHasConnection = false;
+ mHasMainConnection = false;
}
@GuardedBy("ImfLock.class")
@@ -485,8 +485,9 @@
@GuardedBy("ImfLock.class")
private boolean bindCurrentInputMethodServiceMainConnection() {
- mHasConnection = bindCurrentInputMethodService(mMainConnection, mImeConnectionBindFlags);
- return mHasConnection;
+ mHasMainConnection = bindCurrentInputMethodService(mMainConnection,
+ mImeConnectionBindFlags);
+ return mHasMainConnection;
}
/**
@@ -499,7 +500,7 @@
void setCurrentMethodVisible() {
if (mCurMethod != null) {
if (DEBUG) Slog.d(TAG, "setCurrentMethodVisible: mCurToken=" + mCurToken);
- if (hasConnection() && !isVisibleBound()) {
+ if (hasMainConnection() && !isVisibleBound()) {
mVisibleBound = bindCurrentInputMethodService(mVisibleConnection,
IME_VISIBLE_BIND_FLAGS);
}
@@ -507,7 +508,7 @@
}
// No IME is currently connected. Reestablish the main connection.
- if (!hasConnection()) {
+ if (!hasMainConnection()) {
if (DEBUG) {
Slog.d(TAG, "Cannot show input: no IME bound. Rebinding.");
}
@@ -528,7 +529,7 @@
bindCurrentInputMethodServiceMainConnection();
} else {
if (DEBUG) {
- Slog.d(TAG, "Can't show input: connection = " + mHasConnection + ", time = "
+ Slog.d(TAG, "Can't show input: connection = " + mHasMainConnection + ", time = "
+ (TIME_TO_RECONNECT - bindingDuration));
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 131eec3..e58f272 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -603,7 +603,7 @@
*/
@GuardedBy("ImfLock.class")
private boolean hasConnectionLocked() {
- return mBindingController.hasConnection();
+ return mBindingController.hasMainConnection();
}
/** The token tracking the current IME request or {@code null} otherwise. */
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 5b87069..4892c22 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -22,7 +22,6 @@
import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
import static android.media.MediaRouter2Utils.getOriginalId;
import static android.media.MediaRouter2Utils.getProviderId;
-
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.media.MediaFeatureFlagManager.FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE;
@@ -214,17 +213,16 @@
@NonNull
public List<MediaRoute2Info> getSystemRoutes() {
final int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
- final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- == PackageManager.PERMISSION_GRANTED;
+ final boolean hasSystemRoutingPermission = checkCallerHasSystemRoutingPermissions(pid, uid);
final long token = Binder.clearCallingIdentity();
try {
Collection<MediaRoute2Info> systemRoutes;
synchronized (mLock) {
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
- if (hasModifyAudioRoutingPermission) {
+ if (hasSystemRoutingPermission) {
MediaRoute2ProviderInfo providerInfo =
userRecord.mHandler.mSystemProvider.getProviderInfo();
if (providerInfo != null) {
@@ -255,9 +253,8 @@
final boolean hasConfigureWifiDisplayPermission = mContext.checkCallingOrSelfPermission(
android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
== PackageManager.PERMISSION_GRANTED;
- final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- == PackageManager.PERMISSION_GRANTED;
+ final boolean hasModifyAudioRoutingPermission =
+ checkCallerHasModifyAudioRoutingPermission(pid, uid);
final long token = Binder.clearCallingIdentity();
try {
@@ -666,17 +663,17 @@
public RoutingSessionInfo getSystemSessionInfo(
@Nullable String packageName, boolean setDeviceRouteSelected) {
final int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
- final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- == PackageManager.PERMISSION_GRANTED;
+ final boolean hasSystemRoutingPermissions =
+ checkCallerHasSystemRoutingPermissions(pid, uid);
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
List<RoutingSessionInfo> sessionInfos;
- if (hasModifyAudioRoutingPermission) {
+ if (hasSystemRoutingPermissions) {
if (setDeviceRouteSelected) {
// Return a fake system session that shows the device route as selected and
// available bluetooth routes as transferable.
@@ -707,6 +704,25 @@
}
}
+ private boolean checkCallerHasSystemRoutingPermissions(int pid, int uid) {
+ return checkCallerHasModifyAudioRoutingPermission(pid, uid);
+ }
+
+ private boolean checkCallerHasModifyAudioRoutingPermission(int pid, int uid) {
+ return mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_ROUTING, pid, uid)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean checkCallerHasBluetoothPermissions(int pid, int uid) {
+ boolean hasBluetoothRoutingPermission = true;
+ for (String permission : BLUETOOTH_PERMISSIONS_FOR_SYSTEM_ROUTING) {
+ hasBluetoothRoutingPermission &=
+ mContext.checkPermission(permission, pid, uid)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+ return hasBluetoothRoutingPermission;
+ }
+
// End of methods that implements operations for both MediaRouter2 and MediaRouter2Manager.
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
@@ -1587,20 +1603,11 @@
mPid = pid;
mHasConfigureWifiDisplayPermission = hasConfigureWifiDisplayPermission;
mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission;
- mHasBluetoothRoutingPermission = new AtomicBoolean(fetchBluetoothPermission());
+ mHasBluetoothRoutingPermission =
+ new AtomicBoolean(checkCallerHasBluetoothPermissions(mPid, mUid));
mRouterId = mNextRouterOrManagerId.getAndIncrement();
}
- private boolean fetchBluetoothPermission() {
- boolean hasBluetoothRoutingPermission = true;
- for (String permission : BLUETOOTH_PERMISSIONS_FOR_SYSTEM_ROUTING) {
- hasBluetoothRoutingPermission &=
- mContext.checkPermission(permission, mPid, mUid)
- == PackageManager.PERMISSION_GRANTED;
- }
- return hasBluetoothRoutingPermission;
- }
-
/**
* Returns whether the corresponding router has permission to query and control system
* routes.
@@ -1611,7 +1618,7 @@
public void maybeUpdateSystemRoutingPermissionLocked() {
boolean oldSystemRoutingPermissionValue = hasSystemRoutingPermission();
- mHasBluetoothRoutingPermission.set(fetchBluetoothPermission());
+ mHasBluetoothRoutingPermission.set(checkCallerHasBluetoothPermissions(mPid, mUid));
boolean newSystemRoutingPermissionValue = hasSystemRoutingPermission();
if (oldSystemRoutingPermissionValue != newSystemRoutingPermissionValue) {
Map<String, MediaRoute2Info> routesToReport =
@@ -2093,34 +2100,36 @@
if (!hasAddedOrModifiedRoutes && !hasRemovedRoutes) {
return;
}
- List<RouterRecord> routerRecordsWithModifyAudioRoutingPermission =
- getRouterRecords(true);
- List<RouterRecord> routerRecordsWithoutModifyAudioRoutingPermission =
- getRouterRecords(false);
+ List<RouterRecord> routerRecordsWithSystemRoutingPermission =
+ getRouterRecords(/* hasSystemRoutingPermission= */ true);
+ List<RouterRecord> routerRecordsWithoutSystemRoutingPermission =
+ getRouterRecords(/* hasSystemRoutingPermission= */ false);
List<IMediaRouter2Manager> managers = getManagers();
// Managers receive all provider updates with all routes.
notifyRoutesUpdatedToManagers(
managers, new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
- // Routers with modify audio permission (usually system routers) receive all provider
- // updates with all routes.
+ // Routers with system routing access (either via {@link MODIFY_AUDIO_ROUTING} or
+ // {@link BLUETOOTH_CONNECT} + {@link BLUETOOTH_SCAN}) receive all provider updates
+ // with all routes.
notifyRoutesUpdatedToRouterRecords(
- routerRecordsWithModifyAudioRoutingPermission,
+ routerRecordsWithSystemRoutingPermission,
new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
if (!isSystemProvider) {
// Regular routers receive updates from all non-system providers with all non-system
// routes.
notifyRoutesUpdatedToRouterRecords(
- routerRecordsWithoutModifyAudioRoutingPermission,
+ routerRecordsWithoutSystemRoutingPermission,
new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
} else if (hasAddedOrModifiedRoutes) {
- // On system provider updates, regular routers receive the updated default route.
- // This is the only system route they should receive.
+ // On system provider updates, routers without system routing access
+ // receive the updated default route. This is the only system route they should
+ // receive.
mLastNotifiedRoutesToNonPrivilegedRouters.put(defaultRoute.getId(), defaultRoute);
notifyRoutesUpdatedToRouterRecords(
- routerRecordsWithoutModifyAudioRoutingPermission,
+ routerRecordsWithoutSystemRoutingPermission,
new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
}
}
@@ -2526,7 +2535,7 @@
}
}
- private List<RouterRecord> getRouterRecords(boolean hasModifyAudioRoutingPermission) {
+ private List<RouterRecord> getRouterRecords(boolean hasSystemRoutingPermission) {
MediaRouter2ServiceImpl service = mServiceRef.get();
List<RouterRecord> routerRecords = new ArrayList<>();
if (service == null) {
@@ -2534,7 +2543,7 @@
}
synchronized (service.mLock) {
for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
- if (hasModifyAudioRoutingPermission
+ if (hasSystemRoutingPermission
== routerRecord.hasSystemRoutingPermission()) {
routerRecords.add(routerRecord);
}
diff --git a/services/core/java/com/android/server/pm/Android.bp b/services/core/java/com/android/server/pm/Android.bp
deleted file mode 100644
index 89c0124..0000000
--- a/services/core/java/com/android/server/pm/Android.bp
+++ /dev/null
@@ -1,12 +0,0 @@
-aconfig_declarations {
- name: "pm_flags",
- package: "com.android.server.pm",
- srcs: [
- "*.aconfig",
- ],
-}
-
-java_aconfig_library {
- name: "pm_flags_lib",
- aconfig_declarations: "pm_flags",
-}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 69a6c13..ffa2af1 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -58,6 +58,7 @@
import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
import static com.android.server.pm.PackageManagerService.HIDE_EPHEMERAL_APIS;
import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatureArrays;
import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
import static com.android.server.pm.PackageManagerServiceUtils.isSystemOrRootOrShell;
import static com.android.server.pm.resolution.ComponentResolver.RESOLVE_PRIORITY_SORTER;
@@ -4215,8 +4216,7 @@
if (p2SigningDetails == null) {
return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
}
- int result = compareSignatures(p1SigningDetails.getSignatures(),
- p2SigningDetails.getSignatures());
+ int result = compareSignatures(p1SigningDetails, p2SigningDetails);
if (result == PackageManager.SIGNATURE_MATCH) {
return result;
}
@@ -4231,7 +4231,7 @@
Signature[] p2Signatures = p2SigningDetails.hasPastSigningCertificates()
? new Signature[]{p2SigningDetails.getPastSigningCertificates()[0]}
: p2SigningDetails.getSignatures();
- result = compareSignatures(p1Signatures, p2Signatures);
+ result = compareSignatureArrays(p1Signatures, p2Signatures);
}
return result;
}
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 8f7b721..76203ac 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -30,7 +30,6 @@
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageArchiverService;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageDeleteObserver2;
import android.content.pm.IPackageInstaller;
@@ -100,9 +99,6 @@
private final PackageInstallerService mInstallerService;
@NonNull
- private final PackageArchiverService mPackageArchiverService;
-
- @NonNull
private final PackageProperty mPackageProperty;
@NonNull
@@ -131,8 +127,7 @@
@Nullable ComponentName instantAppResolverSettingsComponent,
@NonNull String requiredSupplementalProcessPackage,
@Nullable String servicesExtensionPackageName,
- @Nullable String sharedSystemSharedLibraryPackageName,
- @NonNull PackageArchiverService packageArchiverService) {
+ @Nullable String sharedSystemSharedLibraryPackageName) {
mService = service;
mContext = context;
mDexOptHelper = dexOptHelper;
@@ -148,7 +143,6 @@
mRequiredSupplementalProcessPackage = requiredSupplementalProcessPackage;
mServicesExtensionPackageName = servicesExtensionPackageName;
mSharedSystemSharedLibraryPackageName = sharedSystemSharedLibraryPackageName;
- mPackageArchiverService = packageArchiverService;
}
protected Computer snapshot() {
@@ -622,12 +616,6 @@
@Override
@Deprecated
- public final IPackageArchiverService getPackageArchiverService() {
- return mPackageArchiverService;
- }
-
- @Override
- @Deprecated
public final void getPackageSizeInfo(final String packageName, int userId,
final IPackageStatsObserver observer) {
throw new UnsupportedOperationException(
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index d668146..468b3a7 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -4728,8 +4728,7 @@
synchronized (mPm.mLock) {
platformPkgSetting = mPm.mSettings.getPackageLPr("android");
}
- if (!comparePackageSignatures(platformPkgSetting,
- pkg.getSigningDetails().getSignatures())) {
+ if (!comparePackageSignatures(platformPkgSetting, pkg.getSigningDetails())) {
throw PackageManagerException.ofInternalError("Overlay "
+ pkg.getPackageName()
+ " must target Q or later, "
@@ -4751,8 +4750,7 @@
targetPkgSetting = mPm.mSettings.getPackageLPr(pkg.getOverlayTarget());
}
if (targetPkgSetting != null) {
- if (!comparePackageSignatures(targetPkgSetting,
- pkg.getSigningDetails().getSignatures())) {
+ if (!comparePackageSignatures(targetPkgSetting, pkg.getSigningDetails())) {
// check reference signature
if (mPm.mOverlayConfigSignaturePackage == null) {
throw PackageManagerException.ofInternalError("Overlay "
@@ -4767,8 +4765,7 @@
refPkgSetting = mPm.mSettings.getPackageLPr(
mPm.mOverlayConfigSignaturePackage);
}
- if (!comparePackageSignatures(refPkgSetting,
- pkg.getSigningDetails().getSignatures())) {
+ if (!comparePackageSignatures(refPkgSetting, pkg.getSigningDetails())) {
throw PackageManagerException.ofInternalError("Overlay "
+ pkg.getPackageName() + " signed with a different "
+ "certificate than both the reference package and "
@@ -4799,8 +4796,7 @@
synchronized (mPm.mLock) {
platformPkgSetting = mPm.mSettings.getPackageLPr("android");
}
- if (!comparePackageSignatures(platformPkgSetting,
- pkg.getSigningDetails().getSignatures())) {
+ if (!comparePackageSignatures(platformPkgSetting, pkg.getSigningDetails())) {
throw PackageManagerException.ofInternalError("Apps that share a user with a "
+ "privileged app must themselves be marked as privileged. "
+ pkg.getPackageName() + " shares privileged user "
@@ -4839,10 +4835,8 @@
// to allowlist their privileged permissions just like other
// priv-apps.
PackageSetting platformPkgSetting = mPm.mSettings.getPackageLPr("android");
- if ((compareSignatures(
- platformPkgSetting.getSigningDetails().getSignatures(),
- pkg.getSigningDetails().getSignatures())
- != PackageManager.SIGNATURE_MATCH)) {
+ if ((compareSignatures(platformPkgSetting.getSigningDetails(),
+ pkg.getSigningDetails()) != PackageManager.SIGNATURE_MATCH)) {
scanFlags |= SCAN_AS_PRIVILEGED;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageArchiverService.java b/services/core/java/com/android/server/pm/PackageArchiver.java
similarity index 95%
rename from services/core/java/com/android/server/pm/PackageArchiverService.java
rename to services/core/java/com/android/server/pm/PackageArchiver.java
index e052407..64cdca3 100644
--- a/services/core/java/com/android/server/pm/PackageArchiverService.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -31,10 +31,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
-import android.content.pm.IPackageArchiverService;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
-import android.content.pm.PackageArchiver;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
@@ -74,7 +72,7 @@
* while the data directory is kept. Archived apps are included in the list of launcher apps where
* tapping them re-installs the full app.
*/
-public class PackageArchiverService extends IPackageArchiverService.Stub {
+public class PackageArchiver {
private static final String TAG = "PackageArchiverService";
@@ -93,13 +91,12 @@
@Nullable
private LauncherApps mLauncherApps;
- public PackageArchiverService(Context context, PackageManagerService mPm) {
+ PackageArchiver(Context context, PackageManagerService mPm) {
this.mContext = context;
this.mPm = mPm;
}
- @Override
- public void requestArchive(
+ void requestArchive(
@NonNull String packageName,
@NonNull String callerPackageName,
@NonNull IntentSender intentSender,
@@ -112,10 +109,11 @@
Computer snapshot = mPm.snapshotComputer();
int userId = userHandle.getIdentifier();
int binderUid = Binder.getCallingUid();
- int providedUid = snapshot.getPackageUid(callerPackageName, 0, userId);
+ if (!PackageManagerServiceUtils.isRootOrShell(binderUid)) {
+ verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid);
+ }
snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
"archiveApp");
- verifyCaller(providedUid, binderUid);
CompletableFuture<ArchiveState> archiveStateFuture;
try {
archiveStateFuture = createArchiveState(packageName, userId);
@@ -233,8 +231,7 @@
return true;
}
- @Override
- public void requestUnarchive(
+ void requestUnarchive(
@NonNull String packageName,
@NonNull String callerPackageName,
@NonNull UserHandle userHandle) {
@@ -245,10 +242,11 @@
Computer snapshot = mPm.snapshotComputer();
int userId = userHandle.getIdentifier();
int binderUid = Binder.getCallingUid();
- int providedUid = snapshot.getPackageUid(callerPackageName, 0, userId);
+ if (!PackageManagerServiceUtils.isRootOrShell(binderUid)) {
+ verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid);
+ }
snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
"unarchiveApp");
- verifyCaller(providedUid, binderUid);
PackageStateInternal ps;
try {
ps = getPackageState(packageName, snapshot, binderUid, userId);
@@ -290,8 +288,8 @@
int userId = userHandle.getIdentifier();
Intent unarchiveIntent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE);
unarchiveIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- unarchiveIntent.putExtra(PackageArchiver.EXTRA_UNARCHIVE_PACKAGE_NAME, packageName);
- unarchiveIntent.putExtra(PackageArchiver.EXTRA_UNARCHIVE_ALL_USERS,
+ unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME, packageName);
+ unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_ALL_USERS,
userId == UserHandle.USER_ALL);
unarchiveIntent.setPackage(installerPackage);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index fabef76..95b565d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -18,9 +18,7 @@
import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO;
import static android.os.Process.INVALID_UID;
-
import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
-
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -182,6 +180,8 @@
Manifest.permission.USE_FULL_SCREEN_INTENT
);
+ final PackageArchiver mPackageArchiver;
+
private final Context mContext;
private final PackageManagerService mPm;
private final ApexManager mApexManager;
@@ -301,6 +301,7 @@
apexParserSupplier, mInstallThread.getLooper());
mGentleUpdateHelper = new GentleUpdateHelper(
context, mInstallThread.getLooper(), new AppStateHelper(context));
+ mPackageArchiver = new PackageArchiver(mContext, mPm);
LocalServices.getService(SystemServiceManager.class).startService(
new Lifecycle(context, this));
@@ -1504,6 +1505,24 @@
mSilentUpdatePolicy.setSilentUpdatesThrottleTime(throttleTimeInSeconds);
}
+ @Override
+ public void requestArchive(
+ @NonNull String packageName,
+ @NonNull String callerPackageName,
+ @NonNull IntentSender intentSender,
+ @NonNull UserHandle userHandle) {
+ mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender,
+ userHandle);
+ }
+
+ @Override
+ public void requestUnarchive(
+ @NonNull String packageName,
+ @NonNull String callerPackageName,
+ @NonNull UserHandle userHandle) {
+ mPackageArchiver.requestUnarchive(packageName, callerPackageName, userHandle);
+ }
+
private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
int installerUid) {
int count = 0;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 0dd4111ad..9e0a83c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3396,7 +3396,7 @@
}
if (!isInstalledByAdb(getInstallSource().mInitiatingPackageName)
- && !mPm.mArchiverService.verifySupportsUnarchival(
+ && !mPm.mInstallerService.mPackageArchiver.verifySupportsUnarchival(
getInstallSource().mInstallerPackageName)) {
throw new PackageManagerException(
PackageManager.INSTALL_FAILED_SESSION_INVALID,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e3ff6f6b..d23dcbc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -84,6 +84,7 @@
import android.content.pm.DataLoaderType;
import android.content.pm.FallbackCategoryProvider;
import android.content.pm.FeatureInfo;
+import android.content.pm.Flags;
import android.content.pm.IDexModuleRegisterCallback;
import android.content.pm.IOnChecksumsReadyListener;
import android.content.pm.IPackageDataObserver;
@@ -799,9 +800,6 @@
final SparseArray<VerifyingSession> mPendingEnableRollback = new SparseArray<>();
final PackageInstallerService mInstallerService;
-
- final PackageArchiverService mArchiverService;
-
final ArtManagerService mArtManagerService;
// TODO(b/260124949): Remove these.
@@ -1630,8 +1628,7 @@
(i, pm) -> new CrossProfileIntentFilterHelper(i.getSettings(),
i.getUserManagerService(), i.getLock(), i.getUserManagerInternal(),
context),
- (i, pm) -> new UpdateOwnershipHelper(),
- (i, pm) -> new PackageArchiverService(i.getContext(), pm));
+ (i, pm) -> new UpdateOwnershipHelper());
if (Build.VERSION.SDK_INT <= 0) {
Slog.w(TAG, "**** ro.build.version.sdk not set!");
@@ -1776,7 +1773,6 @@
mFactoryTest = testParams.factoryTest;
mIncrementalManager = testParams.incrementalManager;
mInstallerService = testParams.installerService;
- mArchiverService = testParams.archiverService;
mInstantAppRegistry = testParams.instantAppRegistry;
mChangedPackagesTracker = testParams.changedPackagesTracker;
mInstantAppResolverConnection = testParams.instantAppResolverConnection;
@@ -2356,7 +2352,6 @@
});
mInstallerService = mInjector.getPackageInstallerService();
- mArchiverService = mInjector.getPackageArchiverService();
final ComponentName instantAppResolverComponent = getInstantAppResolver(computer);
if (instantAppResolverComponent != null) {
if (DEBUG_INSTANT) {
@@ -4621,7 +4616,7 @@
mDomainVerificationConnection, mInstallerService, mPackageProperty,
mResolveComponentName, mInstantAppResolverSettingsComponent,
mRequiredSdkSandboxPackage, mServicesExtensionPackageName,
- mSharedSystemSharedLibraryPackageName, mArchiverService);
+ mSharedSystemSharedLibraryPackageName);
}
@Override
@@ -5926,15 +5921,15 @@
}
}
- Signature[] callerSignature;
+ SigningDetails callerSigningDetails;
final int appId = UserHandle.getAppId(callingUid);
Pair<PackageStateInternal, SharedUserApi> either =
snapshot.getPackageOrSharedUser(appId);
if (either != null) {
if (either.first != null) {
- callerSignature = either.first.getSigningDetails().getSignatures();
+ callerSigningDetails = either.first.getSigningDetails();
} else {
- callerSignature = either.second.getSigningDetails().getSignatures();
+ callerSigningDetails = either.second.getSigningDetails();
}
} else {
throw new SecurityException("Unknown calling UID: " + callingUid);
@@ -5943,8 +5938,8 @@
// Verify: can't set installerPackageName to a package that is
// not signed with the same cert as the caller.
if (installerPackageState != null) {
- if (compareSignatures(callerSignature,
- installerPackageState.getSigningDetails().getSignatures())
+ if (compareSignatures(callerSigningDetails,
+ installerPackageState.getSigningDetails())
!= PackageManager.SIGNATURE_MATCH) {
throw new SecurityException(
"Caller does not have same cert as new installer package "
@@ -5960,8 +5955,8 @@
? null : snapshot.getPackageStateInternal(targetInstallerPackageName);
if (targetInstallerPkgSetting != null) {
- if (compareSignatures(callerSignature,
- targetInstallerPkgSetting.getSigningDetails().getSignatures())
+ if (compareSignatures(callerSigningDetails,
+ targetInstallerPkgSetting.getSigningDetails())
!= PackageManager.SIGNATURE_MATCH) {
throw new SecurityException(
"Caller does not have same cert as old installer package "
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 9495279..0c2e082 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -127,8 +127,7 @@
mPreparingPackageParserProducer;
private final Singleton<PackageInstallerService>
mPackageInstallerServiceProducer;
- private final Singleton<PackageArchiverService>
- mPackageArchiverServiceProducer;
+
private final ProducerWithArgument<InstantAppResolverConnection, ComponentName>
mInstantAppResolverConnectionProducer;
private final Singleton<LegacyPermissionManagerInternal>
@@ -187,8 +186,7 @@
Producer<IBackupManager> iBackupManager,
Producer<SharedLibrariesImpl> sharedLibrariesProducer,
Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer,
- Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer,
- Producer<PackageArchiverService> packageArchiverServiceProducer) {
+ Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer) {
mContext = context;
mLock = lock;
mInstaller = installer;
@@ -244,7 +242,6 @@
mCrossProfileIntentFilterHelperProducer = new Singleton<>(
crossProfileIntentFilterHelperProducer);
mUpdateOwnershipHelperProducer = new Singleton<>(updateOwnershipHelperProducer);
- mPackageArchiverServiceProducer = new Singleton<>(packageArchiverServiceProducer);
}
/**
@@ -391,10 +388,6 @@
return mPackageInstallerServiceProducer.get(this, mPackageManager);
}
- public PackageArchiverService getPackageArchiverService() {
- return mPackageArchiverServiceProducer.get(this, mPackageManager);
- }
-
public InstantAppResolverConnection getInstantAppResolverConnection(
ComponentName instantAppResolverComponent) {
return mInstantAppResolverConnectionProducer.produce(
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index b91ce4b..ca57209 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -60,7 +60,6 @@
public @Nullable String incidentReportApproverPackage;
public IncrementalManager incrementalManager;
public PackageInstallerService installerService;
- public PackageArchiverService archiverService;
public InstantAppRegistry instantAppRegistry;
public ChangedPackagesTracker changedPackagesTracker = new ChangedPackagesTracker();
public InstantAppResolverConnection instantAppResolverConnection;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 2028231..1679987 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -403,7 +403,11 @@
* <br />
* {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
*/
- public static int compareSignatures(Signature[] s1, Signature[] s2) {
+ public static int compareSignatures(SigningDetails sd1, SigningDetails sd2) {
+ return compareSignatureArrays(sd1.getSignatures(), sd2.getSignatures());
+ }
+
+ static int compareSignatureArrays(Signature[] s1, Signature[] s2) {
if (s1 == null) {
return s2 == null
? PackageManager.SIGNATURE_NEITHER_SIGNED
@@ -445,10 +449,10 @@
* set or if the signing details of the package are unknown.
*/
public static boolean comparePackageSignatures(PackageSetting pkgSetting,
- Signature[] signatures) {
+ SigningDetails otherSigningDetails) {
final SigningDetails signingDetails = pkgSetting.getSigningDetails();
return signingDetails == SigningDetails.UNKNOWN
- || compareSignatures(signingDetails.getSignatures(), signatures)
+ || compareSignatures(signingDetails, otherSigningDetails)
== PackageManager.SIGNATURE_MATCH;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index a0c9cd1..8d82085 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -26,7 +26,6 @@
import static android.content.pm.PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
import static android.content.pm.PackageManager.RESTRICTION_HIDE_NOTIFICATIONS;
import static android.content.pm.PackageManager.RESTRICTION_NONE;
-
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -334,6 +333,8 @@
return runRenameUser();
case "set-user-restriction":
return runSetUserRestriction();
+ case "get-user-restriction":
+ return runGetUserRestriction();
case "supports-multiple-users":
return runSupportsMultipleUsers();
case "get-max-users":
@@ -379,6 +380,10 @@
return runWaitForHandler(/* forBackgroundHandler= */ false);
case "wait-for-background-handler":
return runWaitForHandler(/* forBackgroundHandler= */ true);
+ case "archive":
+ return runArchive();
+ case "request-unarchive":
+ return runUnarchive();
default: {
if (ART_SERVICE_COMMANDS.contains(cmd)) {
if (DexOptHelper.useArtService()) {
@@ -3405,6 +3410,51 @@
return 0;
}
+ private int runGetUserRestriction() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ int userId = UserHandle.USER_SYSTEM;
+ boolean getAllRestrictions = false;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "--user":
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ case "--all":
+ getAllRestrictions = true;
+ if (getNextArg() != null) {
+ throw new IllegalArgumentException("Argument unexpected after \"--all\"");
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown option " + opt);
+ }
+ }
+
+ final int translatedUserId =
+ translateUserId(userId, UserHandle.USER_NULL, "runGetUserRestriction");
+ final IUserManager um = IUserManager.Stub.asInterface(
+ ServiceManager.getService(Context.USER_SERVICE));
+
+ if (getAllRestrictions) {
+ final Bundle restrictions = um.getUserRestrictions(translatedUserId);
+ pw.println("All restrictions:");
+ pw.println(restrictions.toString());
+ } else {
+ String restriction = getNextArg();
+ if (restriction == null) {
+ throw new IllegalArgumentException("No restriction key specified");
+ }
+ String unexpectedArgument = getNextArg();
+ if (unexpectedArgument != null) {
+ throw new IllegalArgumentException("Argument unexpected after restriction key");
+ }
+ pw.println(um.hasUserRestriction(restriction, translatedUserId));
+ }
+ return 0;
+ }
+
public int runSupportsMultipleUsers() {
getOutPrintWriter().println("Is multiuser supported: "
+ UserManager.supportsMultipleUsers());
@@ -4485,6 +4535,105 @@
}
}
+ private int runArchive() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ int userId = UserHandle.USER_ALL;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT) {
+ UserManagerInternal umi =
+ LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = umi.getUserInfo(userId);
+ if (userInfo == null) {
+ pw.println("Failure [user " + userId + " doesn't exist]");
+ return 1;
+ }
+ }
+ } else {
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ final String packageName = getNextArg();
+ if (packageName == null) {
+ pw.println("Error: package name not specified");
+ return 1;
+ }
+
+ final int translatedUserId =
+ translateUserId(userId, UserHandle.USER_SYSTEM, "runArchive");
+ final LocalIntentReceiver receiver = new LocalIntentReceiver();
+
+ try {
+ mInterface.getPackageInstaller().requestArchive(packageName,
+ /* callerPackageName= */ "", receiver.getIntentSender(),
+ new UserHandle(translatedUserId));
+ } catch (Exception e) {
+ pw.println("Failure [" + e.getMessage() + "]");
+ return 1;
+ }
+
+ final Intent result = receiver.getResult();
+ final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ pw.println("Success");
+ return 0;
+ } else {
+ pw.println("Failure ["
+ + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+ return 1;
+ }
+ }
+
+ private int runUnarchive() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ int userId = UserHandle.USER_ALL;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT) {
+ UserManagerInternal umi =
+ LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = umi.getUserInfo(userId);
+ if (userInfo == null) {
+ pw.println("Failure [user " + userId + " doesn't exist]");
+ return 1;
+ }
+ }
+ } else {
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ final String packageName = getNextArg();
+ if (packageName == null) {
+ pw.println("Error: package name not specified");
+ return 1;
+ }
+
+ final int translatedUserId =
+ translateUserId(userId, UserHandle.USER_SYSTEM, "runArchive");
+
+ try {
+ mInterface.getPackageInstaller().requestUnarchive(packageName,
+ /* callerPackageName= */ "", new UserHandle(translatedUserId));
+ } catch (Exception e) {
+ pw.println("Failure [" + e.getMessage() + "]");
+ return 1;
+ }
+
+ pw.println("Success");
+ return 0;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -4788,6 +4937,12 @@
pw.println("");
pw.println(" set-user-restriction [--user USER_ID] RESTRICTION VALUE");
pw.println("");
+ pw.println(" get-user-restriction [--user USER_ID] [--all] RESTRICTION_KEY");
+ pw.println(" Display the value of restriction for the given restriction key if the");
+ pw.println(" given user is valid.");
+ pw.println(" --all: display all restrictions for the given user");
+ pw.println(" This option is used without restriction key");
+ pw.println("");
pw.println(" get-max-users");
pw.println("");
pw.println(" get-max-running-users");
@@ -4851,6 +5006,16 @@
pw.println(" --timeout: wait for a given number of milliseconds. If the handler(s)");
pw.println(" fail to finish before the timeout, the command returns error.");
pw.println("");
+ pw.println(" archive [--user USER_ID] PACKAGE ");
+ pw.println(" During the archival process, the apps APKs and cache are removed from the");
+ pw.println(" device while the user data is kept. Options are:");
+ pw.println(" --user: archive the app from the given user.");
+ pw.println("");
+ pw.println(" request-unarchive [--user USER_ID] PACKAGE ");
+ pw.println(" Requests to unarchive a currently archived package by sending a request");
+ pw.println(" to unarchive an app to the responsible installer. Options are:");
+ pw.println(" --user: request unarchival of the app from the given user.");
+ pw.println("");
if (DexOptHelper.useArtService()) {
printArtServiceHelp();
} else {
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index a8cdef4..cf5aa7b 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -605,7 +605,7 @@
// Check for exact signature matches across all certs.
Signature[] certs = mCerts.toArray(new Signature[0]);
if (pkg.getSigningDetails() != SigningDetails.UNKNOWN
- && !Signature.areExactMatch(certs, pkg.getSigningDetails().getSignatures())) {
+ && !Signature.areExactMatch(pkg.getSigningDetails(), certs)) {
// certs aren't exact match, but the package may have rotated from the known system cert
if (certs.length > 1 || !pkg.getSigningDetails().hasCertificate(certs[0])) {
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index f4dca3f..0cac790 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -911,8 +911,8 @@
parsedPackage.setSignedWithPlatformKey(
(PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())
|| (platformPkg != null && compareSignatures(
- platformPkg.getSigningDetails().getSignatures(),
- parsedPackage.getSigningDetails().getSignatures()
+ platformPkg.getSigningDetails(),
+ parsedPackage.getSigningDetails()
) == PackageManager.SIGNATURE_MATCH))
);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 803b94b..e365e83 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2750,7 +2750,8 @@
}
}
- private void setUserRestrictionInner(int userId, @NonNull String key, boolean value) {
+ @VisibleForTesting
+ void setUserRestrictionInner(int userId, @NonNull String key, boolean value) {
if (!UserRestrictionsUtils.isValidRestriction(key)) {
Slog.e(LOG_TAG, "Setting invalid restriction " + key);
return;
@@ -4360,11 +4361,11 @@
UserRestrictionsUtils.writeRestrictions(serializer,
mDevicePolicyUserRestrictions.getRestrictions(UserHandle.USER_ALL),
- TAG_DEVICE_POLICY_RESTRICTIONS);
+ TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS);
UserRestrictionsUtils.writeRestrictions(serializer,
mDevicePolicyUserRestrictions.getRestrictions(userInfo.id),
- TAG_DEVICE_POLICY_RESTRICTIONS);
+ TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS);
}
if (userData.account != null) {
diff --git a/services/core/java/com/android/server/pm/flags.aconfig b/services/core/java/com/android/server/pm/flags.aconfig
deleted file mode 100644
index e584801..0000000
--- a/services/core/java/com/android/server/pm/flags.aconfig
+++ /dev/null
@@ -1,9 +0,0 @@
-package: "com.android.server.pm"
-
-flag {
- name: "quarantined_enabled"
- namespace: "package_manager_service"
- description: "Feature flag for Quarantined state"
- bug: "269127435"
-}
-
diff --git a/services/core/java/com/android/server/pm/pkg/SuspendParams.java b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
index 4e08106..86391c9 100644
--- a/services/core/java/com/android/server/pm/pkg/SuspendParams.java
+++ b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
@@ -17,6 +17,7 @@
package com.android.server.pm.pkg;
import android.annotation.Nullable;
+import android.content.pm.Flags;
import android.content.pm.SuspendDialogInfo;
import android.os.BaseBundle;
import android.os.PersistableBundle;
@@ -24,7 +25,6 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.pm.Flags;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 81c2f07..812e228 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -3267,8 +3267,7 @@
if (existingSigningDetails == SigningDetails.UNKNOWN) {
return verified;
} else {
- if (!Signature.areExactMatch(existingSigningDetails.getSignatures(),
- verified.getResult().getSignatures())) {
+ if (!Signature.areExactMatch(existingSigningDetails, verified.getResult())) {
return input.error(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
baseCodePath + " has mismatched certificates");
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 9905ddf..635e11b 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -159,10 +159,26 @@
private VirtualDeviceManagerInternal mVirtualDeviceManager;
private enum TrustState {
- UNTRUSTED, // the phone is not unlocked by any trustagents
- TRUSTABLE, // the phone is in a semi-locked state that can be unlocked if
- // FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE is passed and a trustagent is trusted
- TRUSTED // the phone is unlocked
+ // UNTRUSTED means that TrustManagerService is currently *not* giving permission for the
+ // user's Keyguard to be dismissed, and grants of trust by trust agents are remembered in
+ // the corresponding TrustAgentWrapper but are not recognized until the device is unlocked
+ // for the user. I.e., if the device is locked and the state is UNTRUSTED, it cannot be
+ // unlocked by a trust agent. Automotive devices are an exception; grants of trust are
+ // always recognized on them.
+ UNTRUSTED,
+
+ // TRUSTABLE is the same as UNTRUSTED except that new grants of trust using
+ // FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE are recognized for moving to TRUSTED. I.e., if
+ // the device is locked and the state is TRUSTABLE, it can be unlocked by a trust agent,
+ // provided that the trust agent chooses to use Active Unlock. The TRUSTABLE state is only
+ // possible as a result of a downgrade from TRUSTED, after a trust agent used
+ // FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE in its most recent grant.
+ TRUSTABLE,
+
+ // TRUSTED means that TrustManagerService is currently giving permission for the user's
+ // Keyguard to be dismissed. This implies that the device is unlocked for the user (where
+ // the case of Keyguard showing but dismissible just with swipe counts as "unlocked").
+ TRUSTED
};
@GuardedBy("mUserTrustState")
@@ -744,6 +760,12 @@
}
}
+ private TrustState getUserTrustStateInner(int userId) {
+ synchronized (mUserTrustState) {
+ return mUserTrustState.get(userId, TrustState.UNTRUSTED);
+ }
+ }
+
boolean isDeviceLockedInner(int userId) {
synchronized (mDeviceLockedForUser) {
return mDeviceLockedForUser.get(userId, true);
@@ -806,7 +828,12 @@
continue;
}
- boolean trusted = aggregateIsTrusted(id);
+ final boolean trusted;
+ if (android.security.Flags.fixUnlockedDeviceRequiredKeys()) {
+ trusted = getUserTrustStateInner(id) == TrustState.TRUSTED;
+ } else {
+ trusted = aggregateIsTrusted(id);
+ }
boolean showingKeyguard = true;
boolean biometricAuthenticated = false;
boolean currentUserIsUnlocked = false;
@@ -1627,7 +1654,7 @@
if (isCurrent) {
fout.print(" (current)");
}
- fout.print(": trusted=" + dumpBool(aggregateIsTrusted(user.id)));
+ fout.print(": trustState=" + getUserTrustStateInner(user.id));
fout.print(", trustManaged=" + dumpBool(aggregateIsTrustManaged(user.id)));
fout.print(", deviceLocked=" + dumpBool(isDeviceLockedInner(user.id)));
fout.print(", isActiveUnlockRunning=" + dumpBool(
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index ddc0519..cfc7031 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -326,9 +326,6 @@
if (DEBUG) {
Slog.d(TAG, "publish system wallpaper changed!");
}
- if (localSync != null) {
- localSync.complete();
- }
notifyWallpaperChanged(wallpaper);
}
};
@@ -336,7 +333,7 @@
// If this was the system wallpaper, rebind...
bindWallpaperComponentLocked(mImageWallpaper, true, false, wallpaper,
callback);
- notifyColorsWhich |= FLAG_SYSTEM;
+ notifyColorsWhich |= wallpaper.mWhich;
}
if (lockWallpaperChanged) {
@@ -350,9 +347,6 @@
if (DEBUG) {
Slog.d(TAG, "publish lock wallpaper changed!");
}
- if (localSync != null) {
- localSync.complete();
- }
notifyWallpaperChanged(wallpaper);
}
};
@@ -377,9 +371,8 @@
}
saveSettingsLocked(wallpaper.userId);
- // Notify the client immediately if only lockscreen wallpaper changed.
- if (lockWallpaperChanged && !sysWallpaperChanged) {
- notifyWallpaperChanged(wallpaper);
+ if ((sysWallpaperChanged || lockWallpaperChanged) && localSync != null) {
+ localSync.complete();
}
}
@@ -1426,7 +1419,6 @@
lockWp.connection.mWallpaper = lockWp;
mOriginalSystem.mWhich = FLAG_LOCK;
updateEngineFlags(mOriginalSystem);
- notifyWallpaperColorsChanged(lockWp, FLAG_LOCK);
} else {
// Failed rename, use current system wp for both
if (DEBUG) {
@@ -1446,7 +1438,6 @@
updateEngineFlags(mOriginalSystem);
mLockWallpaperMap.put(mNewWallpaper.userId, mOriginalSystem);
mLastLockWallpaper = mOriginalSystem;
- notifyWallpaperColorsChanged(mOriginalSystem, FLAG_LOCK);
}
} else if (mNewWallpaper.mWhich == FLAG_LOCK) {
// New wp is lock only, so old system+lock is now system only
@@ -1460,10 +1451,7 @@
}
}
}
-
- synchronized (mLock) {
- saveSettingsLocked(mNewWallpaper.userId);
- }
+ saveSettingsLocked(mNewWallpaper.userId);
if (DEBUG) {
Slog.v(TAG, "--- wallpaper changed --");
@@ -3340,7 +3328,6 @@
if (DEBUG) {
Slog.d(TAG, "publish system wallpaper changed!");
}
- liveSync.complete();
}
};
@@ -3396,6 +3383,7 @@
}
mLockWallpaperMap.remove(newWallpaper.userId);
}
+ if (liveSync != null) liveSync.complete();
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -3514,6 +3502,11 @@
}
// Has the component changed?
if (!force && changingToSame(componentName, wallpaper)) {
+ try {
+ if (reply != null) reply.sendResult(null);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to send callback", e);
+ }
return true;
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index b823e73..74d3417 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -492,6 +492,8 @@
final boolean res;
final boolean finishWithRootActivity =
finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
+ mTaskSupervisor.getBackgroundActivityLaunchController()
+ .onActivityRequestedFinishing(r);
if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
|| (finishWithRootActivity && r == rootR)) {
// If requested, remove the task that is associated to this activity only if it
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 582536b..b2eb383 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -324,7 +324,6 @@
import android.util.EventLog;
import android.util.Log;
import android.util.MergedConfiguration;
-import android.util.Pair;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -725,9 +724,9 @@
private final boolean mIsUserAlwaysVisible;
/** Allow activity launches which would otherwise be blocked by
- * {@link ActivityTransitionSecurityController#checkActivityAllowedToStart}
+ * {@link BackgroundActivityStartController#checkActivityAllowedToStart}
*/
- private boolean mAllowCrossUidActivitySwitchFromBelow;
+ boolean mAllowCrossUidActivitySwitchFromBelow;
/** Have we been asked to have this token keep the screen frozen? */
private boolean mFreezingScreen;
@@ -10191,50 +10190,6 @@
mAllowCrossUidActivitySwitchFromBelow = allowed;
}
- /**
- * Determines if a source is allowed to add or remove activities from the task,
- * if the current ActivityRecord is above it in the stack
- *
- * A transition is blocked ({@code false} returned) if all of the following are met:
- * <pre>
- * 1. The source activity and the current activity record belong to different apps
- * (i.e, have different UIDs).
- * 2. Both the source activity and the current activity target U+
- * 3. The current activity has not set
- * {@link ActivityRecord#setAllowCrossUidActivitySwitchFromBelow(boolean)} to {@code true}
- * </pre>
- *
- * Returns a pair where the elements mean:
- * <pre>
- * First: {@code false} if we should actually block the transition (takes into consideration
- * feature flag and targetSdk).
- * Second: {@code false} if we should warn about the transition via toasts. This happens if
- * the transition would be blocked in case both the app was targeting U+ and the feature was
- * enabled.
- * </pre>
- *
- * @param sourceUid The source (s) activity performing the state change
- */
- Pair<Boolean, Boolean> allowCrossUidActivitySwitchFromBelow(int sourceUid) {
- int myUid = info.applicationInfo.uid;
- if (sourceUid == myUid) {
- return new Pair<>(true, true);
- }
-
- // If mAllowCrossUidActivitySwitchFromBelow is set, honor it.
- if (mAllowCrossUidActivitySwitchFromBelow) {
- return new Pair<>(true, true);
- }
-
- // If it is not set, default to true if both records target ≥ U, false otherwise
- // TODO(b/258792202) Replace with CompatChanges and replace Pair with boolean once feature
- // flag is removed
- boolean restrictActivitySwitch =
- ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(myUid)
- && ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(sourceUid);
- return new Pair<>(!restrictActivitySwitch, false);
- }
-
boolean getTurnScreenOnFlag() {
return mTurnScreenOn || containsTurnScreenOnWindow();
}
diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
index 19d8129..f1a2159 100644
--- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
+++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
@@ -43,7 +43,7 @@
static final String DOC_LINK = "go/android-asm";
/** Used to determine which version of the ASM logic was used in logs while we iterate */
- static final int ASM_VERSION = 7;
+ static final int ASM_VERSION = 8;
private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER;
private static final String KEY_ASM_PREFIX = "ActivitySecurity__";
@@ -53,7 +53,7 @@
private static final String KEY_ASM_EXEMPTED_PACKAGES = KEY_ASM_PREFIX
+ "asm_exempted_packages";
private static final int VALUE_DISABLE = 0;
- private static final int VALUE_ENABLE_FOR_U = 1;
+ private static final int VALUE_ENABLE_FOR_V = 1;
private static final int VALUE_ENABLE_FOR_ALL = 2;
private static final int DEFAULT_VALUE = VALUE_DISABLE;
@@ -84,7 +84,7 @@
private static boolean flagEnabledForUid(int flag, int uid) {
boolean flagEnabled = flag == VALUE_ENABLE_FOR_ALL
- || (flag == VALUE_ENABLE_FOR_U
+ || (flag == VALUE_ENABLE_FOR_V
&& CompatChanges.isChangeEnabled(ASM_RESTRICTIONS, uid));
if (flagEnabled) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index a6e5040..c39b266 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -98,8 +98,6 @@
/** Whether an {@link ActivityStarter} is currently executing (starting an Activity). */
private boolean mInExecution = false;
- private final BackgroundActivityStartController mBalController;
-
/**
* TODO(b/64750076): Capture information necessary for dump and
* {@link #postStartActivityProcessingForLastStarter} rather than keeping the entire object
@@ -122,7 +120,6 @@
mFactory.setController(this);
mPendingRemoteAnimationRegistry = new PendingRemoteAnimationRegistry(service.mGlobalLock,
service.mH);
- mBalController = new BackgroundActivityStartController(mService, mSupervisor);
}
/**
@@ -666,8 +663,4 @@
pw.println("(nothing)");
}
}
-
- BackgroundActivityStartController getBackgroundActivityLaunchController() {
- return mBalController;
- }
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 458d1e8..27315bb 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -74,16 +74,8 @@
import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_COMPONENT;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_UID;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_DEFAULT;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PENDING_INTENT;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_SAW_PERMISSION;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
-import static com.android.server.wm.BackgroundActivityStartController.balCodeToString;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
@@ -126,18 +118,14 @@
import android.os.UserManager;
import android.service.voice.IVoiceInteractionSession;
import android.text.TextUtils;
-import android.util.Pair;
import android.util.Pools.SynchronizedPool;
import android.util.Slog;
-import android.widget.Toast;
import android.window.RemoteTransition;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.HeavyWeightSwitcherActivity;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.UiThread;
import com.android.server.am.PendingIntentRecord;
import com.android.server.pm.InstantAppResolver;
import com.android.server.power.ShutdownCheckPoints;
@@ -151,10 +139,6 @@
import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.Date;
-import java.util.StringJoiner;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Predicate;
/**
* Controller for interpreting how and then launching an activity.
@@ -188,7 +172,7 @@
* Feature flag for go/activity-security rules
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
static final long ASM_RESTRICTIONS = 230590090L;
private final ActivityTaskManagerService mService;
@@ -1114,7 +1098,7 @@
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
"shouldAbortBackgroundActivityStart");
BackgroundActivityStartController balController =
- mController.getBackgroundActivityLaunchController();
+ mSupervisor.getBackgroundActivityLaunchController();
balCode =
balController.checkBackgroundActivityStart(
callingUid,
@@ -1841,6 +1825,9 @@
sourceRecord, "launch-into-pip");
}
+ mSupervisor.getBackgroundActivityLaunchController()
+ .onNewActivityLaunched(mStartActivity);
+
return START_SUCCESS;
}
@@ -1973,7 +1960,9 @@
}
}
- if (!checkActivitySecurityModel(r, newTask, targetTask)) {
+ if (!mSupervisor.getBackgroundActivityLaunchController().checkActivityAllowedToStart(
+ mSourceRecord, r, newTask, targetTask, mLaunchFlags, mBalCode, mCallingUid,
+ mRealCallingUid)) {
return START_ABORTED;
}
@@ -1981,226 +1970,6 @@
}
/**
- * TODO(b/263368846): Shift to BackgroundActivityStartController once class is ready
- * Log activity starts which violate one of the following rules of the
- * activity security model (ASM):
- * See go/activity-security for rationale behind the rules.
- * 1. Within a task, only an activity matching a top UID of the task can start activities
- * 2. Only activities within a foreground task, which match a top UID of the task, can
- * create a new task or bring an existing one into the foreground
- */
- private boolean checkActivitySecurityModel(ActivityRecord r, boolean newTask, Task targetTask) {
- // BAL Exception allowed in all cases
- if (mBalCode == BAL_ALLOW_ALLOWLISTED_UID) {
- return true;
- }
-
- // Intents with FLAG_ACTIVITY_NEW_TASK will always be considered as creating a new task
- // even if the intent is delivered to an existing task.
- boolean taskToFront = newTask
- || (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == FLAG_ACTIVITY_NEW_TASK;
-
- // BAL exception only allowed for new tasks
- if (taskToFront) {
- if (mBalCode == BAL_ALLOW_ALLOWLISTED_COMPONENT
- || mBalCode == BAL_ALLOW_PERMISSION
- || mBalCode == BAL_ALLOW_PENDING_INTENT
- || mBalCode == BAL_ALLOW_SAW_PERMISSION
- || mBalCode == BAL_ALLOW_VISIBLE_WINDOW) {
- return true;
- }
- }
-
- Pair<Boolean, Boolean> pair = null;
- if (mSourceRecord != null) {
- boolean passesAsmChecks = true;
- Task sourceTask = mSourceRecord.getTask();
-
- // Allow launching into a new task (or a task matching the launched activity's
- // affinity) only if the current task is foreground or mutating its own task.
- // The latter can happen eg. if caller uses NEW_TASK flag and the activity being
- // launched matches affinity of source task.
- if (taskToFront) {
- passesAsmChecks = sourceTask != null
- && (sourceTask.isVisible() || sourceTask == targetTask);
- }
-
- if (passesAsmChecks) {
- Task taskToCheck = taskToFront ? sourceTask : targetTask;
- pair = ActivityTaskSupervisor
- .doesTopActivityMatchingUidExistForAsm(taskToCheck, mSourceRecord.getUid(),
- mSourceRecord);
- }
- } else if (!taskToFront) {
- // We don't have a sourceRecord, and we're launching into an existing task.
- // Allow if callingUid is top of stack.
- pair = ActivityTaskSupervisor
- .doesTopActivityMatchingUidExistForAsm(targetTask, mCallingUid,
- /*sourceRecord*/null);
- }
-
- boolean shouldBlockActivityStart = true;
- if (pair != null) {
- // We block if feature flag is enabled
- shouldBlockActivityStart = !pair.first;
- // Used for logging/toasts. Would we block if target sdk was U and feature was
- // enabled? If so, we can't return here but we also might not block at the end
- boolean wouldBlockActivityStartIgnoringFlags = !pair.second;
-
- if (!wouldBlockActivityStartIgnoringFlags) {
- return true;
- }
- }
-
- // ASM rules have failed. Log why
- return logAsmFailureAndCheckFeatureEnabled(r, newTask, targetTask, shouldBlockActivityStart,
- taskToFront);
- }
-
- private boolean logAsmFailureAndCheckFeatureEnabled(ActivityRecord r, boolean newTask,
- Task targetTask, boolean shouldBlockActivityStart, boolean taskToFront) {
- // ASM rules have failed. Log why
- ActivityRecord targetTopActivity = targetTask == null ? null
- : targetTask.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
-
- int action = newTask || mSourceRecord == null
- ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_NEW_TASK
- : (mSourceRecord.getTask().equals(targetTask)
- ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_SAME_TASK
- : FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_DIFFERENT_TASK);
-
- boolean blockActivityStartAndFeatureEnabled = ActivitySecurityModelFeatureFlags
- .shouldRestrictActivitySwitch(mCallingUid)
- && shouldBlockActivityStart;
-
- String asmDebugInfo = getDebugInfoForActivitySecurity("Launch", r, targetTask,
- targetTopActivity, blockActivityStartAndFeatureEnabled, /*taskToFront*/taskToFront);
-
- FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
- /* caller_uid */
- mSourceRecord != null ? mSourceRecord.getUid() : mCallingUid,
- /* caller_activity_class_name */
- mSourceRecord != null ? mSourceRecord.info.name : null,
- /* target_task_top_activity_uid */
- targetTopActivity != null ? targetTopActivity.getUid() : -1,
- /* target_task_top_activity_class_name */
- targetTopActivity != null ? targetTopActivity.info.name : null,
- /* target_task_is_different */
- newTask || mSourceRecord == null || targetTask == null
- || !targetTask.equals(mSourceRecord.getTask()),
- /* target_activity_uid */
- r.getUid(),
- /* target_activity_class_name */
- r.info.name,
- /* target_intent_action */
- r.intent.getAction(),
- /* target_intent_flags */
- mLaunchFlags,
- /* action */
- action,
- /* version */
- ActivitySecurityModelFeatureFlags.ASM_VERSION,
- /* multi_window - we have our source not in the target task, but both are visible */
- targetTask != null && mSourceRecord != null
- && !targetTask.equals(mSourceRecord.getTask()) && targetTask.isVisible(),
- /* bal_code */
- mBalCode,
- /* task_stack */
- asmDebugInfo
- );
-
- String launchedFromPackageName = r.launchedFromPackage;
- if (ActivitySecurityModelFeatureFlags.shouldShowToast(mCallingUid)) {
- String toastText = ActivitySecurityModelFeatureFlags.DOC_LINK
- + (blockActivityStartAndFeatureEnabled ? " blocked " : " would block ")
- + getApplicationLabel(mService.mContext.getPackageManager(),
- launchedFromPackageName);
- UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
- toastText, Toast.LENGTH_LONG).show());
-
- Slog.i(TAG, asmDebugInfo);
- }
-
- if (blockActivityStartAndFeatureEnabled) {
- Slog.e(TAG, "[ASM] Abort Launching r: " + r
- + " as source: "
- + (mSourceRecord != null ? mSourceRecord : launchedFromPackageName)
- + " is in background. New task: " + newTask
- + ". Top activity: " + targetTopActivity
- + ". BAL Code: " + balCodeToString(mBalCode));
-
- return false;
- }
-
- return true;
- }
-
- /** Only called when an activity launch may be blocked, which should happen very rarely */
- private String getDebugInfoForActivitySecurity(String action, ActivityRecord r, Task targetTask,
- ActivityRecord targetTopActivity, boolean blockActivityStartAndFeatureEnabled,
- boolean taskToFront) {
- final String prefix = "[ASM] ";
- Function<ActivityRecord, String> recordToString = (ar) -> {
- if (ar == null) {
- return null;
- }
- return (ar == mSourceRecord ? " [source]=> "
- : ar == targetTopActivity ? " [ top ]=> "
- : ar == r ? " [target]=> "
- : " => ")
- + ar
- + " :: visible=" + ar.isVisible()
- + ", finishing=" + ar.isFinishing()
- + ", alwaysOnTop=" + ar.isAlwaysOnTop()
- + ", taskFragment=" + ar.getTaskFragment();
- };
-
- StringJoiner joiner = new StringJoiner("\n");
- joiner.add(prefix + "------ Activity Security " + action + " Debug Logging Start ------");
- joiner.add(prefix + "Block Enabled: " + blockActivityStartAndFeatureEnabled);
- joiner.add(prefix + "ASM Version: " + ActivitySecurityModelFeatureFlags.ASM_VERSION);
-
- boolean targetTaskMatchesSourceTask = targetTask != null
- && mSourceRecord != null && mSourceRecord.getTask() == targetTask;
-
- if (mSourceRecord == null) {
- joiner.add(prefix + "Source Package: " + r.launchedFromPackage);
- String realCallingPackage = mService.mContext.getPackageManager().getNameForUid(
- mRealCallingUid);
- joiner.add(prefix + "Real Calling Uid Package: " + realCallingPackage);
- } else {
- joiner.add(prefix + "Source Record: " + recordToString.apply(mSourceRecord));
- if (targetTaskMatchesSourceTask) {
- joiner.add(prefix + "Source/Target Task: " + mSourceRecord.getTask());
- joiner.add(prefix + "Source/Target Task Stack: ");
- } else {
- joiner.add(prefix + "Source Task: " + mSourceRecord.getTask());
- joiner.add(prefix + "Source Task Stack: ");
- }
- mSourceRecord.getTask().forAllActivities((Consumer<ActivityRecord>)
- ar -> joiner.add(prefix + recordToString.apply(ar)));
- }
-
- joiner.add(prefix + "Target Task Top: " + recordToString.apply(targetTopActivity));
- if (!targetTaskMatchesSourceTask) {
- joiner.add(prefix + "Target Task: " + targetTask);
- if (targetTask != null) {
- joiner.add(prefix + "Target Task Stack: ");
- targetTask.forAllActivities((Consumer<ActivityRecord>)
- ar -> joiner.add(prefix + recordToString.apply(ar)));
- }
- }
-
- joiner.add(prefix + "Target Record: " + recordToString.apply(r));
- joiner.add(prefix + "Intent: " + mIntent);
- joiner.add(prefix + "TaskToFront: " + taskToFront);
- joiner.add(prefix + "BalCode: " + balCodeToString(mBalCode));
-
- joiner.add(prefix + "------ Activity Security " + action + " Debug Logging End ------");
- return joiner.toString();
- }
-
- /**
* Returns whether embedding of {@code starting} is allowed.
*
* @param taskFragment the TaskFragment for embedding.
@@ -2284,8 +2053,9 @@
reusedTask != null ? reusedTask.getTopNonFinishingActivity() : null, intentGrants);
if (mAddingToTask) {
- clearTopIfNeeded(targetTask, mCallingUid, mRealCallingUid, mStartActivity.getUid(),
- mLaunchFlags);
+ mSupervisor.getBackgroundActivityLaunchController().clearTopIfNeeded(targetTask,
+ mSourceRecord, mStartActivity, mCallingUid, mRealCallingUid, mLaunchFlags,
+ mBalCode);
return START_SUCCESS;
}
@@ -2318,65 +2088,6 @@
}
/**
- * If the top activity uid does not match the launching or launched activity, and the launch was
- * not requested from the top uid, we want to clear out all non matching activities to prevent
- * the top activity being sandwiched.
- *
- * Both creator and sender UID are considered for the launching activity.
- */
- private void clearTopIfNeeded(@NonNull Task targetTask, int callingUid, int realCallingUid,
- int startingUid, int launchFlags) {
- if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != FLAG_ACTIVITY_NEW_TASK
- || mBalCode == BAL_ALLOW_ALLOWLISTED_UID) {
- // Launch is from the same task, (a top or privileged UID), or is directly privileged.
- return;
- }
-
- Predicate<ActivityRecord> isLaunchingOrLaunched = ar ->
- ar.isUid(startingUid) || ar.isUid(callingUid) || ar.isUid(realCallingUid);
-
- // Return early if we know for sure we won't need to clear any activities by just checking
- // the top activity.
- ActivityRecord targetTaskTop = targetTask.getTopMostActivity();
- if (targetTaskTop == null || isLaunchingOrLaunched.test(targetTaskTop)) {
- return;
- }
-
- // Find the first activity which matches a safe UID and is not finishing. Clear everything
- // above it
- boolean shouldBlockActivityStart = ActivitySecurityModelFeatureFlags
- .shouldRestrictActivitySwitch(callingUid);
- int[] finishCount = new int[0];
- if (shouldBlockActivityStart) {
- ActivityRecord activity = targetTask.getActivity(isLaunchingOrLaunched);
- if (activity == null) {
- // mStartActivity is not in task, so clear everything
- activity = mStartActivity;
- }
-
- finishCount = new int[1];
- targetTask.performClearTop(activity, launchFlags, finishCount);
- if (finishCount[0] > 0) {
- Slog.w(TAG, "Cleared top n: " + finishCount[0] + " activities from task t: "
- + targetTask + " not matching top uid: " + callingUid);
- }
- }
-
- if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)
- && (!shouldBlockActivityStart || finishCount[0] > 0)) {
- UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
- (shouldBlockActivityStart
- ? "Top activities cleared by "
- : "Top activities would be cleared by ")
- + ActivitySecurityModelFeatureFlags.DOC_LINK,
- Toast.LENGTH_LONG).show());
-
- getDebugInfoForActivitySecurity("Clear Top", mStartActivity, targetTask, targetTaskTop,
- shouldBlockActivityStart, /* taskToFront */ true);
- }
- }
-
- /**
* Check if the activity being launched is the same as the one currently at the top and it
* should only be launched once.
*/
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index fd42077..6999c6a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -68,7 +68,6 @@
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
-
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -2221,7 +2220,7 @@
callerApp = getProcessController(appThread);
}
final BackgroundActivityStartController balController =
- getActivityStartController().getBackgroundActivityLaunchController();
+ mTaskSupervisor.getBackgroundActivityLaunchController();
if (balController.shouldAbortBackgroundActivityStart(
callingUid,
callingPid,
@@ -3660,6 +3659,9 @@
synchronized (mGlobalLock) {
if (r.getParent() == null) {
Slog.e(TAG, "Skip enterPictureInPictureMode, destroyed " + r);
+ if (transition != null) {
+ transition.abort();
+ }
return;
}
EventLogTags.writeWmEnterPip(r.mUserId, System.identityHashCode(r),
@@ -5628,6 +5630,15 @@
}
}
+ void registerCompatScaleProvider(@CompatScaleProvider.CompatScaleModeOrderId int id,
+ @NonNull CompatScaleProvider provider) {
+ mCompatModePackages.registerCompatScaleProvider(id, provider);
+ }
+
+ void unregisterCompatScaleProvider(@CompatScaleProvider.CompatScaleModeOrderId int id) {
+ mCompatModePackages.unregisterCompatScaleProvider(id);
+ }
+
/**
* Returns {@code true} if the process represented by the pid passed as argument is
* instrumented and the instrumentation source was granted with the permission also
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 6eb9ed69..69e679d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -134,12 +134,10 @@
import android.provider.MediaStore;
import android.util.ArrayMap;
import android.util.MergedConfiguration;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.Display;
-import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -147,10 +145,8 @@
import com.android.internal.content.ReferrerIntent;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
-import com.android.server.UiThread;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.HostingRecord;
import com.android.server.am.UserState;
@@ -256,6 +252,9 @@
final ActivityTaskManagerService mService;
RootWindowContainer mRootWindowContainer;
+ /** Helper class for checking if an activity transition meets security rules */
+ BackgroundActivityStartController mBalController;
+
/** The historial list of recent tasks including inactive tasks */
RecentTasks mRecentTasks;
@@ -466,6 +465,8 @@
mLaunchParamsPersister = new LaunchParamsPersister(mPersisterQueue, this);
mLaunchParamsController = new LaunchParamsController(mService, mLaunchParamsPersister);
mLaunchParamsController.registerDefaultModifiers(this);
+
+ mBalController = new BackgroundActivityStartController(mService, this);
}
void onSystemReady() {
@@ -1284,6 +1285,10 @@
return mAppOpsManager;
}
+ BackgroundActivityStartController getBackgroundActivityLaunchController() {
+ return mBalController;
+ }
+
private int getComponentRestrictionForCallingPackage(ActivityInfo activityInfo,
String callingPackage, @Nullable String callingFeatureId, int callingPid,
int callingUid, boolean ignoreTargetSecurity) {
@@ -1700,172 +1705,12 @@
if (task.isPersistable) {
mService.notifyTaskPersisterLocked(null, true);
}
- checkActivitySecurityForTaskClear(callingUid, task, callerActivityClassName);
+ mBalController
+ .checkActivityAllowedToClearTask(task, callingUid, callerActivityClassName);
} finally {
task.mInRemoveTask = false;
}
}
-
- // TODO(b/263368846) Move to live with the rest of the ASM logic.
- /**
- * Returns home if the passed in callingUid is not top of the stack, rather than returning to
- * previous task.
- */
- private void checkActivitySecurityForTaskClear(int callingUid, Task task,
- String callerActivityClassName) {
- // We may have already checked that the callingUid has additional clearTask privileges, and
- // cleared the calling identify. If so, we infer we do not need further restrictions here.
- if (callingUid == SYSTEM_UID || !task.isVisible() || task.inMultiWindowMode()) {
- return;
- }
-
- TaskDisplayArea displayArea = task.getTaskDisplayArea();
- if (displayArea == null) {
- // If there is no associated display area, we can not return home.
- return;
- }
-
- Pair<Boolean, Boolean> pair = doesTopActivityMatchingUidExistForAsm(task, callingUid, null);
- boolean shouldBlockActivitySwitchIfFeatureEnabled = !pair.first;
- boolean wouldBlockActivitySwitchIgnoringFlags = !pair.second;
-
- if (!wouldBlockActivitySwitchIgnoringFlags) {
- return;
- }
-
- ActivityRecord topActivity = task.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
- FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
- /* caller_uid */
- callingUid,
- /* caller_activity_class_name */
- callerActivityClassName,
- /* target_task_top_activity_uid */
- topActivity == null ? -1 : topActivity.getUid(),
- /* target_task_top_activity_class_name */
- topActivity == null ? null : topActivity.info.name,
- /* target_task_is_different */
- false,
- /* target_activity_uid */
- -1,
- /* target_activity_class_name */
- null,
- /* target_intent_action */
- null,
- /* target_intent_flags */
- 0,
- /* action */
- FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
- /* version */
- ActivitySecurityModelFeatureFlags.ASM_VERSION,
- /* multi_window */
- false,
- /* bal_code */
- -1,
- /* task_stack */
- null
- );
-
- boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags
- .shouldRestrictActivitySwitch(callingUid)
- && shouldBlockActivitySwitchIfFeatureEnabled;
-
- PackageManager pm = mService.mContext.getPackageManager();
- String callingPackage = pm.getNameForUid(callingUid);
- final CharSequence callingLabel;
- if (callingPackage == null) {
- callingPackage = String.valueOf(callingUid);
- callingLabel = callingPackage;
- } else {
- callingLabel = getApplicationLabel(pm, callingPackage);
- }
-
- if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
- UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
- (ActivitySecurityModelFeatureFlags.DOC_LINK
- + (restrictActivitySwitch ? " returned home due to "
- : " would return home due to ")
- + callingLabel), Toast.LENGTH_LONG).show());
- }
-
- // If the activity switch should be restricted, return home rather than the
- // previously top task, to prevent users from being confused which app they're
- // viewing
- if (restrictActivitySwitch) {
- Slog.w(TAG, "[ASM] Return to home as source: " + callingPackage
- + " is not on top of task t: " + task);
- displayArea.moveHomeActivityToTop("taskRemoved");
- } else {
- Slog.i(TAG, "[ASM] Would return to home as source: " + callingPackage
- + " is not on top of task t: " + task);
- }
- }
-
- /**
- * For the purpose of ASM, ‘Top UID” for a task is defined as an activity UID
- * 1. Which is top of the stack in z-order
- * a. Excluding any activities with the flag ‘isAlwaysOnTop’ and
- * b. Excluding any activities which are `finishing`
- * 2. Or top of an adjacent task fragment to (1)
- *
- * The 'sourceRecord' can be considered top even if it is 'finishing'
- *
- * @return A pair where the first value is the return value matching the checks above, and the
- * second value is the return value disregarding the feature flag or target api levels. Use the
- * first value for blocking launches - the second value is only used to determine if a toast
- * should be displayed, and will be used alongside a feature flag in {@link ActivityStarter}.
- */
- // TODO(b/263368846) Shift to BackgroundActivityStartController once class is ready
- @Nullable
- static Pair<Boolean, Boolean> doesTopActivityMatchingUidExistForAsm(@Nullable Task task,
- int uid, @Nullable ActivityRecord sourceRecord) {
- // If the source is visible, consider it 'top'.
- if (sourceRecord != null && sourceRecord.isVisible()) {
- return new Pair<>(true, true);
- }
-
- // Always allow actual top activity to clear task
- ActivityRecord topActivity = task.getTopMostActivity();
- if (topActivity != null && topActivity.isUid(uid)) {
- return new Pair<>(true, true);
- }
-
- // Consider the source activity, whether or not it is finishing. Do not consider any other
- // finishing activity.
- Predicate<ActivityRecord> topOfStackPredicate = (ar) -> ar.equals(sourceRecord)
- || (!ar.finishing && !ar.isAlwaysOnTop());
-
- // Check top of stack (or the first task fragment for embedding).
- topActivity = task.getActivity(topOfStackPredicate);
- if (topActivity == null) {
- return new Pair<>(false, false);
- }
-
- Pair<Boolean, Boolean> pair = topActivity.allowCrossUidActivitySwitchFromBelow(uid);
- if (pair.first) {
- return new Pair<>(true, pair.second);
- }
-
- // Even if the top activity is not a match, we may be in an embedded activity scenario with
- // an adjacent task fragment. Get the second fragment.
- TaskFragment taskFragment = topActivity.getTaskFragment();
- if (taskFragment == null) {
- return new Pair<>(false, false);
- }
-
- TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
- if (adjacentTaskFragment == null) {
- return new Pair<>(false, false);
- }
-
- // Check the second fragment.
- topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate);
- if (topActivity == null) {
- return new Pair<>(false, false);
- }
-
- return topActivity.allowCrossUidActivitySwitchFromBelow(uid);
- }
-
static CharSequence getApplicationLabel(PackageManager pm, String packageName) {
try {
ApplicationInfo launchedFromPackageInfo = pm.getApplicationInfo(
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index de38a20..761b0a8 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -122,8 +122,7 @@
callerApp = mService.getProcessController(appThread);
}
final BackgroundActivityStartController balController =
- mService.getActivityStartController()
- .getBackgroundActivityLaunchController();
+ mService.mTaskSupervisor.getBackgroundActivityLaunchController();
if (balController.shouldAbortBackgroundActivityStart(
callingUid,
callingPid,
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 188f4d0..4bafccd 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -17,7 +17,9 @@
package com.android.server.wm;
import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.SYSTEM_UID;
import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
import static com.android.internal.util.Preconditions.checkState;
@@ -26,10 +28,13 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
+import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
+import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -45,12 +50,19 @@
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.Slog;
+import android.widget.Toast;
-
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.UiThread;
import com.android.server.am.PendingIntentRecord;
import java.lang.annotation.Retention;
+import java.util.HashMap;
+import java.util.StringJoiner;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
/**
* Helper class to check permissions for starting Activities.
@@ -65,6 +77,9 @@
public static final String VERDICT_WOULD_BE_ALLOWED_IF_SENDER_GRANTS_BAL =
"Activity start would be allowed if the sender granted BAL privileges";
+ private static final long ASM_GRACEPERIOD_TIMEOUT_MS = TIMEOUT_MS;
+ private static final int ASM_GRACEPERIOD_MAX_REPEATS = 5;
+
private final ActivityTaskManagerService mService;
private final ActivityTaskSupervisor mSupervisor;
@@ -148,6 +163,11 @@
}
}
+ @GuardedBy("mService.mGlobalLock")
+ private HashMap<Integer, FinishedActivityEntry> mTaskIdToFinishedActivity = new HashMap<>();
+ @GuardedBy("mService.mGlobalLock")
+ private FinishedActivityEntry mTopFinishedActivity = null;
+
BackgroundActivityStartController(
final ActivityTaskManagerService service, final ActivityTaskSupervisor supervisor) {
mService = service;
@@ -575,6 +595,501 @@
return BAL_BLOCK;
}
+ /**
+ * Log activity starts which violate one of the following rules of the
+ * activity security model (ASM):
+ * See go/activity-security for rationale behind the rules.
+ * 1. Within a task, only an activity matching a top UID of the task can start activities
+ * 2. Only activities within a foreground task, which match a top UID of the task, can
+ * create a new task or bring an existing one into the foreground
+ */
+ boolean checkActivityAllowedToStart(@Nullable ActivityRecord sourceRecord,
+ @NonNull ActivityRecord targetRecord, boolean newTask, @NonNull Task targetTask,
+ int launchFlags, int balCode, int callingUid, int realCallingUid) {
+ // BAL Exception allowed in all cases
+ if (balCode == BAL_ALLOW_ALLOWLISTED_UID) {
+ return true;
+ }
+
+ // Intents with FLAG_ACTIVITY_NEW_TASK will always be considered as creating a new task
+ // even if the intent is delivered to an existing task.
+ boolean taskToFront = newTask
+ || (launchFlags & FLAG_ACTIVITY_NEW_TASK) == FLAG_ACTIVITY_NEW_TASK;
+
+ // BAL exception only allowed for new tasks
+ if (taskToFront) {
+ if (balCode == BAL_ALLOW_ALLOWLISTED_COMPONENT
+ || balCode == BAL_ALLOW_PERMISSION
+ || balCode == BAL_ALLOW_PENDING_INTENT
+ || balCode == BAL_ALLOW_SAW_PERMISSION
+ || balCode == BAL_ALLOW_VISIBLE_WINDOW) {
+ return true;
+ }
+ }
+
+ if (balCode == BAL_ALLOW_GRACE_PERIOD) {
+ if (taskToFront && mTopFinishedActivity != null
+ && mTopFinishedActivity.mUid == callingUid) {
+ return true;
+ } else if (!taskToFront) {
+ FinishedActivityEntry finishedEntry =
+ mTaskIdToFinishedActivity.get(targetTask.mTaskId);
+ if (finishedEntry != null && finishedEntry.mUid == callingUid) {
+ return true;
+ }
+ }
+ }
+
+ BlockActivityStart bas = null;
+ if (sourceRecord != null) {
+ boolean passesAsmChecks = true;
+ Task sourceTask = sourceRecord.getTask();
+
+ // Allow launching into a new task (or a task matching the launched activity's
+ // affinity) only if the current task is foreground or mutating its own task.
+ // The latter can happen eg. if caller uses NEW_TASK flag and the activity being
+ // launched matches affinity of source task.
+ if (taskToFront) {
+ passesAsmChecks = sourceTask != null
+ && (sourceTask.isVisible() || sourceTask == targetTask);
+ }
+
+ if (passesAsmChecks) {
+ Task taskToCheck = taskToFront ? sourceTask : targetTask;
+ bas = isTopActivityMatchingUidAbsentForAsm(taskToCheck, sourceRecord.getUid(),
+ sourceRecord);
+ }
+ } else if (!taskToFront) {
+ // We don't have a sourceRecord, and we're launching into an existing task.
+ // Allow if callingUid is top of stack.
+ bas = isTopActivityMatchingUidAbsentForAsm(targetTask, callingUid,
+ /*sourceRecord*/null);
+ }
+
+ if (bas != null && !bas.mWouldBlockActivityStartIgnoringFlag) {
+ return true;
+ }
+
+ // ASM rules have failed. Log why
+ return logAsmFailureAndCheckFeatureEnabled(sourceRecord, callingUid, realCallingUid,
+ newTask, targetTask, targetRecord, balCode, launchFlags, bas, taskToFront);
+ }
+
+ private boolean logAsmFailureAndCheckFeatureEnabled(ActivityRecord sourceRecord, int callingUid,
+ int realCallingUid, boolean newTask, Task targetTask, ActivityRecord targetRecord,
+ @BalCode int balCode, int launchFlags, BlockActivityStart bas, boolean taskToFront) {
+
+ ActivityRecord targetTopActivity = targetTask == null ? null
+ : targetTask.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
+
+ int action = newTask || sourceRecord == null
+ ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_NEW_TASK
+ : (sourceRecord.getTask().equals(targetTask)
+ ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_SAME_TASK
+ : FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_DIFFERENT_TASK);
+
+ boolean blockActivityStartAndFeatureEnabled = ActivitySecurityModelFeatureFlags
+ .shouldRestrictActivitySwitch(callingUid)
+ && (bas == null || bas.mBlockActivityStartIfFlagEnabled);
+
+ String asmDebugInfo = getDebugInfoForActivitySecurity("Launch", sourceRecord,
+ targetRecord, targetTask, targetTopActivity, realCallingUid, balCode,
+ blockActivityStartAndFeatureEnabled, taskToFront);
+
+ FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
+ /* caller_uid */
+ sourceRecord != null ? sourceRecord.getUid() : callingUid,
+ /* caller_activity_class_name */
+ sourceRecord != null ? sourceRecord.info.name : null,
+ /* target_task_top_activity_uid */
+ targetTopActivity != null ? targetTopActivity.getUid() : -1,
+ /* target_task_top_activity_class_name */
+ targetTopActivity != null ? targetTopActivity.info.name : null,
+ /* target_task_is_different */
+ newTask || sourceRecord == null || targetTask == null
+ || !targetTask.equals(sourceRecord.getTask()),
+ /* target_activity_uid */
+ targetRecord.getUid(),
+ /* target_activity_class_name */
+ targetRecord.info.name,
+ /* target_intent_action */
+ targetRecord.intent.getAction(),
+ /* target_intent_flags */
+ launchFlags,
+ /* action */
+ action,
+ /* version */
+ ActivitySecurityModelFeatureFlags.ASM_VERSION,
+ /* multi_window - we have our source not in the target task, but both are visible */
+ targetTask != null && sourceRecord != null
+ && !targetTask.equals(sourceRecord.getTask()) && targetTask.isVisible(),
+ /* bal_code */
+ balCode,
+ /* debug_info */
+ asmDebugInfo
+ );
+
+ String launchedFromPackageName = targetRecord.launchedFromPackage;
+ if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
+ String toastText = ActivitySecurityModelFeatureFlags.DOC_LINK
+ + (blockActivityStartAndFeatureEnabled ? " blocked " : " would block ")
+ + getApplicationLabel(mService.mContext.getPackageManager(),
+ launchedFromPackageName);
+ UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
+ toastText, Toast.LENGTH_LONG).show());
+
+ Slog.i(TAG, asmDebugInfo);
+ }
+
+ if (blockActivityStartAndFeatureEnabled) {
+ Slog.e(TAG, "[ASM] Abort Launching r: " + targetRecord
+ + " as source: "
+ + (sourceRecord != null ? sourceRecord : launchedFromPackageName)
+ + " is in background. New task: " + newTask
+ + ". Top activity: " + targetTopActivity
+ + ". BAL Code: " + balCodeToString(balCode));
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * If the top activity uid does not match the launching or launched activity, and the launch was
+ * not requested from the top uid, we want to clear out all non matching activities to prevent
+ * the top activity being sandwiched.
+ * Both creator and sender UID are considered for the launching activity.
+ */
+ void clearTopIfNeeded(@NonNull Task targetTask, @Nullable ActivityRecord sourceRecord,
+ @NonNull ActivityRecord targetRecord, int callingUid, int realCallingUid,
+ int launchFlags, @BalCode int balCode) {
+ if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != FLAG_ACTIVITY_NEW_TASK
+ || balCode == BAL_ALLOW_ALLOWLISTED_UID) {
+ // Launch is from the same task, (a top or privileged UID), or is directly privileged.
+ return;
+ }
+
+ int startingUid = targetRecord.getUid();
+ Predicate<ActivityRecord> isLaunchingOrLaunched = ar ->
+ ar.isUid(startingUid) || ar.isUid(callingUid) || ar.isUid(realCallingUid);
+
+ // Return early if we know for sure we won't need to clear any activities by just checking
+ // the top activity.
+ ActivityRecord targetTaskTop = targetTask.getTopMostActivity();
+ if (targetTaskTop == null || isLaunchingOrLaunched.test(targetTaskTop)) {
+ return;
+ }
+
+ // Find the first activity which matches a safe UID and is not finishing. Clear everything
+ // above it
+ boolean shouldBlockActivityStart = ActivitySecurityModelFeatureFlags
+ .shouldRestrictActivitySwitch(callingUid);
+ int[] finishCount = new int[0];
+ if (shouldBlockActivityStart) {
+ ActivityRecord activity = targetTask.getActivity(isLaunchingOrLaunched);
+ if (activity == null) {
+ // mStartActivity is not in task, so clear everything
+ activity = targetRecord;
+ }
+
+ finishCount = new int[1];
+ targetTask.performClearTop(activity, launchFlags, finishCount);
+ if (finishCount[0] > 0) {
+ Slog.w(TAG, "Cleared top n: " + finishCount[0] + " activities from task t: "
+ + targetTask + " not matching top uid: " + callingUid);
+ }
+ }
+
+ if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)
+ && (!shouldBlockActivityStart || finishCount[0] > 0)) {
+ UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
+ (shouldBlockActivityStart
+ ? "Top activities cleared by "
+ : "Top activities would be cleared by ")
+ + ActivitySecurityModelFeatureFlags.DOC_LINK,
+ Toast.LENGTH_LONG).show());
+
+ Slog.i(TAG, getDebugInfoForActivitySecurity("Clear Top", sourceRecord, targetRecord,
+ targetTask, targetTaskTop, realCallingUid, balCode, shouldBlockActivityStart,
+ /* taskToFront */ true));
+ }
+ }
+
+ /**
+ * Returns home if the passed in callingUid is not top of the stack, rather than returning to
+ * previous task.
+ */
+ void checkActivityAllowedToClearTask(@NonNull Task task, int callingUid,
+ @NonNull String callerActivityClassName) {
+ // We may have already checked that the callingUid has additional clearTask privileges, and
+ // cleared the calling identify. If so, we infer we do not need further restrictions here.
+ if (callingUid == SYSTEM_UID || !task.isVisible() || task.inMultiWindowMode()) {
+ return;
+ }
+
+ TaskDisplayArea displayArea = task.getTaskDisplayArea();
+ if (displayArea == null) {
+ // If there is no associated display area, we can not return home.
+ return;
+ }
+
+ BlockActivityStart bas = isTopActivityMatchingUidAbsentForAsm(task, callingUid, null);
+ if (!bas.mWouldBlockActivityStartIgnoringFlag) {
+ return;
+ }
+
+ ActivityRecord topActivity = task.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
+ FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
+ /* caller_uid */
+ callingUid,
+ /* caller_activity_class_name */
+ callerActivityClassName,
+ /* target_task_top_activity_uid */
+ topActivity == null ? -1 : topActivity.getUid(),
+ /* target_task_top_activity_class_name */
+ topActivity == null ? null : topActivity.info.name,
+ /* target_task_is_different */
+ false,
+ /* target_activity_uid */
+ -1,
+ /* target_activity_class_name */
+ null,
+ /* target_intent_action */
+ null,
+ /* target_intent_flags */
+ 0,
+ /* action */
+ FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
+ /* version */
+ ActivitySecurityModelFeatureFlags.ASM_VERSION,
+ /* multi_window */
+ false,
+ /* bal_code */
+ -1,
+ /* debug_info */
+ null
+ );
+
+ boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags
+ .shouldRestrictActivitySwitch(callingUid)
+ && bas.mBlockActivityStartIfFlagEnabled;
+
+ PackageManager pm = mService.mContext.getPackageManager();
+ String callingPackage = pm.getNameForUid(callingUid);
+ final CharSequence callingLabel;
+ if (callingPackage == null) {
+ callingPackage = String.valueOf(callingUid);
+ callingLabel = callingPackage;
+ } else {
+ callingLabel = getApplicationLabel(pm, callingPackage);
+ }
+
+ if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
+ UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
+ (ActivitySecurityModelFeatureFlags.DOC_LINK
+ + (restrictActivitySwitch ? " returned home due to "
+ : " would return home due to ")
+ + callingLabel), Toast.LENGTH_LONG).show());
+ }
+
+ // If the activity switch should be restricted, return home rather than the
+ // previously top task, to prevent users from being confused which app they're
+ // viewing
+ if (restrictActivitySwitch) {
+ Slog.w(TAG, "[ASM] Return to home as source: " + callingPackage
+ + " is not on top of task t: " + task);
+ displayArea.moveHomeActivityToTop("taskRemoved");
+ } else {
+ Slog.i(TAG, "[ASM] Would return to home as source: " + callingPackage
+ + " is not on top of task t: " + task);
+ }
+ }
+
+ /**
+ * For the purpose of ASM, ‘Top UID” for a task is defined as an activity UID
+ * 1. Which is top of the stack in z-order
+ * a. Excluding any activities with the flag ‘isAlwaysOnTop’ and
+ * b. Excluding any activities which are `finishing`
+ * 2. Or top of an adjacent task fragment to (1)
+ * <p>
+ * The 'sourceRecord' can be considered top even if it is 'finishing'
+ *
+ * Returns a class where the elements are:
+ * <pre>
+ * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into
+ * consideration feature flag and targetSdk).
+ * wouldBlockActivityStartIgnoringFlags: {@code true} if we should warn about the transition via
+ * toasts. This happens if the transition would be blocked in case both the app was targeting V+
+ * and the feature was enabled.
+ * </pre>
+ */
+ private BlockActivityStart isTopActivityMatchingUidAbsentForAsm(@NonNull Task task,
+ int uid, @Nullable ActivityRecord sourceRecord) {
+ // If the source is visible, consider it 'top'.
+ if (sourceRecord != null && sourceRecord.isVisible()) {
+ return new BlockActivityStart(false, false);
+ }
+
+ // Always allow actual top activity to clear task
+ ActivityRecord topActivity = task.getTopMostActivity();
+ if (topActivity != null && topActivity.isUid(uid)) {
+ return new BlockActivityStart(false, false);
+ }
+
+ // Consider the source activity, whether or not it is finishing. Do not consider any other
+ // finishing activity.
+ Predicate<ActivityRecord> topOfStackPredicate = (ar) -> ar.equals(sourceRecord)
+ || (!ar.finishing && !ar.isAlwaysOnTop());
+
+ // Check top of stack (or the first task fragment for embedding).
+ topActivity = task.getActivity(topOfStackPredicate);
+ if (topActivity == null) {
+ return new BlockActivityStart(true, true);
+ }
+
+ BlockActivityStart pair = blockCrossUidActivitySwitchFromBelow(topActivity, uid);
+ if (!pair.mBlockActivityStartIfFlagEnabled) {
+ return pair;
+ }
+
+ // Even if the top activity is not a match, we may be in an embedded activity scenario with
+ // an adjacent task fragment. Get the second fragment.
+ TaskFragment taskFragment = topActivity.getTaskFragment();
+ if (taskFragment == null) {
+ return pair;
+ }
+
+ TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+ if (adjacentTaskFragment == null) {
+ return pair;
+ }
+
+ // Check the second fragment.
+ topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate);
+ if (topActivity == null) {
+ return new BlockActivityStart(true, true);
+ }
+
+ return blockCrossUidActivitySwitchFromBelow(topActivity, uid);
+ }
+
+ /**
+ * Determines if a source is allowed to add or remove activities from the task,
+ * if the current ActivityRecord is above it in the stack
+ *
+ * A transition is blocked ({@code false} returned) if all of the following are met:
+ * <pre>
+ * 1. The source activity and the current activity record belong to different apps
+ * (i.e, have different UIDs).
+ * 2. Both the source activity and the current activity target U+
+ * 3. The current activity has not set
+ * {@link ActivityRecord#setAllowCrossUidActivitySwitchFromBelow(boolean)} to {@code true}
+ * </pre>
+ *
+ * Returns a class where the elements are:
+ * <pre>
+ * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into
+ * consideration feature flag and targetSdk).
+ * wouldBlockActivityStartIgnoringFlags: {@code true} if we should warn about the transition via
+ * toasts. This happens if the transition would be blocked in case both the app was targeting V+
+ * and the feature was enabled.
+ * </pre>
+ *
+ * @param sourceUid The source (s) activity performing the state change
+ */
+ private BlockActivityStart blockCrossUidActivitySwitchFromBelow(ActivityRecord ar,
+ int sourceUid) {
+ if (ar.isUid(sourceUid)) {
+ return new BlockActivityStart(false, false);
+ }
+
+ // If mAllowCrossUidActivitySwitchFromBelow is set, honor it.
+ if (ar.mAllowCrossUidActivitySwitchFromBelow) {
+ return new BlockActivityStart(false, false);
+ }
+
+ // At this point, we would block if the feature is launched and both apps were V+
+ // Since we have a feature flag, we need to check that too
+ // TODO(b/258792202) Replace with CompatChanges and replace Pair with boolean once feature
+ // flag is removed
+ boolean restrictActivitySwitch =
+ ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(ar.getUid())
+ && ActivitySecurityModelFeatureFlags
+ .shouldRestrictActivitySwitch(sourceUid);
+ return new BackgroundActivityStartController
+ .BlockActivityStart(restrictActivitySwitch, true);
+ }
+
+ /**
+ * Only called when an activity launch may be blocked, which should happen very rarely
+ */
+ private String getDebugInfoForActivitySecurity(@NonNull String action,
+ @Nullable ActivityRecord sourceRecord, @NonNull ActivityRecord targetRecord,
+ @Nullable Task targetTask, @Nullable ActivityRecord targetTopActivity,
+ int realCallingUid, @BalCode int balCode,
+ boolean blockActivityStartAndFeatureEnabled, boolean taskToFront) {
+ final String prefix = "[ASM] ";
+ Function<ActivityRecord, String> recordToString = (ar) -> {
+ if (ar == null) {
+ return null;
+ }
+ return (ar == sourceRecord ? " [source]=> "
+ : ar == targetTopActivity ? " [ top ]=> "
+ : ar == targetRecord ? " [target]=> "
+ : " => ")
+ + ar
+ + " :: visible=" + ar.isVisible()
+ + ", finishing=" + ar.isFinishing()
+ + ", alwaysOnTop=" + ar.isAlwaysOnTop()
+ + ", taskFragment=" + ar.getTaskFragment();
+ };
+
+ StringJoiner joiner = new StringJoiner("\n");
+ joiner.add(prefix + "------ Activity Security " + action + " Debug Logging Start ------");
+ joiner.add(prefix + "Block Enabled: " + blockActivityStartAndFeatureEnabled);
+ joiner.add(prefix + "ASM Version: " + ActivitySecurityModelFeatureFlags.ASM_VERSION);
+
+ boolean targetTaskMatchesSourceTask = targetTask != null
+ && sourceRecord != null && sourceRecord.getTask() == targetTask;
+
+ if (sourceRecord == null) {
+ joiner.add(prefix + "Source Package: " + targetRecord.launchedFromPackage);
+ String realCallingPackage = mService.mContext.getPackageManager().getNameForUid(
+ realCallingUid);
+ joiner.add(prefix + "Real Calling Uid Package: " + realCallingPackage);
+ } else {
+ joiner.add(prefix + "Source Record: " + recordToString.apply(sourceRecord));
+ if (targetTaskMatchesSourceTask) {
+ joiner.add(prefix + "Source/Target Task: " + sourceRecord.getTask());
+ joiner.add(prefix + "Source/Target Task Stack: ");
+ } else {
+ joiner.add(prefix + "Source Task: " + sourceRecord.getTask());
+ joiner.add(prefix + "Source Task Stack: ");
+ }
+ sourceRecord.getTask().forAllActivities((Consumer<ActivityRecord>)
+ ar -> joiner.add(prefix + recordToString.apply(ar)));
+ }
+
+ joiner.add(prefix + "Target Task Top: " + recordToString.apply(targetTopActivity));
+ if (!targetTaskMatchesSourceTask) {
+ joiner.add(prefix + "Target Task: " + targetTask);
+ if (targetTask != null) {
+ joiner.add(prefix + "Target Task Stack: ");
+ targetTask.forAllActivities((Consumer<ActivityRecord>)
+ ar -> joiner.add(prefix + recordToString.apply(ar)));
+ }
+ }
+
+ joiner.add(prefix + "Target Record: " + recordToString.apply(targetRecord));
+ joiner.add(prefix + "Intent: " + targetRecord.intent);
+ joiner.add(prefix + "TaskToFront: " + taskToFront);
+ joiner.add(prefix + "BalCode: " + balCodeToString(balCode));
+
+ joiner.add(prefix + "------ Activity Security " + action + " Debug Logging End ------");
+ return joiner.toString();
+ }
+
static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, boolean background,
int callingUid, int realCallingUid, Intent intent, int pid, String msg) {
return logStartAllowedAndReturnCode(code, background, callingUid, realCallingUid, intent,
@@ -653,4 +1168,91 @@
realCallingUid);
}
}
+
+ /**
+ * Called whenever an activity finishes. Stores the record, so it can be used by ASM grace
+ * period checks.
+ */
+ void onActivityRequestedFinishing(@NonNull ActivityRecord finishActivity) {
+ // We only update the entry if the passed in activity
+ // 1. Has been chained less than a set max AND
+ // 2. Is visible or top
+ FinishedActivityEntry entry =
+ mTaskIdToFinishedActivity.get(finishActivity.getTask().mTaskId);
+ if (entry != null && finishActivity.isUid(entry.mUid)
+ && entry.mLaunchCount > ASM_GRACEPERIOD_MAX_REPEATS) {
+ return;
+ }
+
+ if (!finishActivity.mVisibleRequested
+ && finishActivity != finishActivity.getTask().getTopMostActivity()) {
+ return;
+ }
+
+ FinishedActivityEntry newEntry = new FinishedActivityEntry(finishActivity);
+ mTaskIdToFinishedActivity.put(finishActivity.getTask().mTaskId, newEntry);
+ if (finishActivity.getTask().mVisibleRequested) {
+ mTopFinishedActivity = newEntry;
+ }
+ }
+
+ /**
+ * Called whenever an activity starts. Updates the record so the activity is no longer
+ * considered for ASM grace period checks
+ */
+ void onNewActivityLaunched(ActivityRecord activityStarted) {
+ if (activityStarted.getTask() == null) {
+ return;
+ }
+
+ if (activityStarted.getTask().mVisibleRequested) {
+ mTopFinishedActivity = null;
+ }
+
+ FinishedActivityEntry entry =
+ mTaskIdToFinishedActivity.get(activityStarted.getTask().mTaskId);
+ if (entry != null && activityStarted.getTask().isTaskId(entry.mTaskId)) {
+ mTaskIdToFinishedActivity.remove(entry.mTaskId);
+ }
+ }
+
+ static class BlockActivityStart {
+ // We should block if feature flag is enabled
+ private final boolean mBlockActivityStartIfFlagEnabled;
+ // Used for logging/toasts. Would we block if target sdk was V and feature was
+ // enabled?
+ private final boolean mWouldBlockActivityStartIgnoringFlag;
+
+ BlockActivityStart(boolean shouldBlockActivityStart,
+ boolean wouldBlockActivityStartIgnoringFlags) {
+ this.mBlockActivityStartIfFlagEnabled = shouldBlockActivityStart;
+ this.mWouldBlockActivityStartIgnoringFlag = wouldBlockActivityStartIgnoringFlags;
+ }
+ }
+
+ private class FinishedActivityEntry {
+ int mUid;
+ int mTaskId;
+ int mLaunchCount;
+
+ FinishedActivityEntry(ActivityRecord ar) {
+ FinishedActivityEntry entry = mTaskIdToFinishedActivity.get(ar.getTask().mTaskId);
+ int taskId = ar.getTask().mTaskId;
+ this.mUid = ar.getUid();
+ this.mTaskId = taskId;
+ this.mLaunchCount = entry == null || !ar.isUid(entry.mUid) ? 1 : entry.mLaunchCount + 1;
+
+ mService.mH.postDelayed(() -> {
+ synchronized (mService.mGlobalLock) {
+ if (mTaskIdToFinishedActivity.get(taskId) == this) {
+ mTaskIdToFinishedActivity.remove(taskId);
+ }
+
+ if (mTopFinishedActivity == this) {
+ mTopFinishedActivity = null;
+ }
+ }
+ }, ASM_GRACEPERIOD_TIMEOUT_MS);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index c6978fd..2439159 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -20,7 +20,10 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_SYSTEM_FIRST;
+import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_SYSTEM_LAST;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.GameManagerInternal;
@@ -32,6 +35,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.res.CompatibilityInfo;
+import android.content.res.CompatibilityInfo.CompatScale;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Handler;
@@ -332,6 +336,8 @@
private final HashMap<String, Integer> mPackages = new HashMap<>();
private final CompatHandler mHandler;
+ private final SparseArray<CompatScaleProvider> mProviders = new SparseArray<>();
+
public CompatModePackages(ActivityTaskManagerService service, File systemDir, Handler handler) {
mService = service;
mFile = new AtomicFile(new File(systemDir, "packages-compat.xml"), "compat-mode");
@@ -441,13 +447,38 @@
public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
final boolean forceCompat = getPackageCompatModeEnabledLocked(ai);
- final float compatScale = getCompatScale(ai.packageName, ai.uid);
+ final CompatScale compatScale = getCompatScaleFromProvider(ai.packageName, ai.uid);
+ final float appScale = compatScale != null
+ ? compatScale.mScaleFactor
+ : getCompatScale(ai.packageName, ai.uid, /* checkProvider= */ false);
+ final float densityScale = compatScale != null ? compatScale.mDensityScaleFactor : appScale;
final Configuration config = mService.getGlobalConfiguration();
return new CompatibilityInfo(ai, config.screenLayout, config.smallestScreenWidthDp,
- forceCompat, compatScale);
+ forceCompat, appScale, densityScale);
}
float getCompatScale(String packageName, int uid) {
+ return getCompatScale(packageName, uid, /* checkProvider= */ true);
+ }
+
+ private CompatScale getCompatScaleFromProvider(String packageName, int uid) {
+ for (int i = 0; i < mProviders.size(); i++) {
+ final CompatScaleProvider provider = mProviders.valueAt(i);
+ final CompatScale compatScale = provider.getCompatScale(packageName, uid);
+ if (compatScale != null) {
+ return compatScale;
+ }
+ }
+ return null;
+ }
+
+ private float getCompatScale(String packageName, int uid, boolean checkProviders) {
+ if (checkProviders) {
+ final CompatScale compatScale = getCompatScaleFromProvider(packageName, uid);
+ if (compatScale != null) {
+ return compatScale.mScaleFactor;
+ }
+ }
final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
if (mGameManager == null) {
mGameManager = LocalServices.getService(GameManagerInternal.class);
@@ -487,6 +518,36 @@
return 1f;
}
+ void registerCompatScaleProvider(@CompatScaleProvider.CompatScaleModeOrderId int id,
+ @NonNull CompatScaleProvider provider) {
+ synchronized (mService.mGlobalLock) {
+ if (mProviders.contains(id)) {
+ throw new IllegalArgumentException("Duplicate id provided: " + id);
+ }
+ if (provider == null) {
+ throw new IllegalArgumentException("The passed CompatScaleProvider "
+ + "can not be null");
+ }
+ if (!CompatScaleProvider.isValidOrderId(id)) {
+ throw new IllegalArgumentException(
+ "Provided id " + id + " is not in range of valid ids for system "
+ + "services [" + COMPAT_SCALE_MODE_SYSTEM_FIRST + ","
+ + COMPAT_SCALE_MODE_SYSTEM_LAST + "]");
+ }
+ mProviders.put(id, provider);
+ }
+ }
+
+ void unregisterCompatScaleProvider(@CompatScaleProvider.CompatScaleModeOrderId int id) {
+ synchronized (mService.mGlobalLock) {
+ if (!mProviders.contains(id)) {
+ throw new IllegalArgumentException(
+ "CompatScaleProvider with id (" + id + ") is not registered");
+ }
+ mProviders.remove(id);
+ }
+ }
+
private static float getScalingFactor(String packageName, UserHandle userHandle) {
if (CompatChanges.isChangeEnabled(DOWNSCALE_90, packageName, userHandle)) {
return 0.9f;
diff --git a/services/core/java/com/android/server/wm/CompatScaleProvider.java b/services/core/java/com/android/server/wm/CompatScaleProvider.java
new file mode 100644
index 0000000..5474ece
--- /dev/null
+++ b/services/core/java/com/android/server/wm/CompatScaleProvider.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.CompatibilityInfo.CompatScale;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An interface for services that need to provide compatibility scale different than
+ * the default android compatibility.
+ */
+public interface CompatScaleProvider {
+
+ /**
+ * The unique id of each provider registered by a system service which determines the order
+ * it will execute in.
+ */
+ @IntDef(prefix = { "COMPAT_SCALE_MODE_" }, value = {
+ // Order Ids for system services
+ COMPAT_SCALE_MODE_SYSTEM_FIRST,
+ COMPAT_SCALE_MODE_GAME,
+ COMPAT_SCALE_MODE_PRODUCT,
+ COMPAT_SCALE_MODE_SYSTEM_LAST, // Update this when adding new ids
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface CompatScaleModeOrderId {}
+
+ /**
+ * The first id, used by the framework to determine the valid range of ids.
+ * @hide
+ */
+ int COMPAT_SCALE_MODE_SYSTEM_FIRST = 0;
+
+ /**
+ * TODO(b/295207384)
+ * The identifier for {@link android.app.GameManagerInternal} provider
+ * @hide
+ */
+ int COMPAT_SCALE_MODE_GAME = 1;
+
+ /**
+ * The identifier for a provider which is specific to the type of android product like
+ * Automotive, Wear, TV etc.
+ * @hide
+ */
+ int COMPAT_SCALE_MODE_PRODUCT = 2;
+
+ /**
+ * The final id, used by the framework to determine the valid range of ids. Update this when
+ * adding new ids.
+ * @hide
+ */
+ int COMPAT_SCALE_MODE_SYSTEM_LAST = COMPAT_SCALE_MODE_PRODUCT;
+
+ /**
+ * Returns {@code true} if the id is in the range of valid system services
+ * @hide
+ */
+ static boolean isValidOrderId(int id) {
+ return (id >= COMPAT_SCALE_MODE_SYSTEM_FIRST && id <= COMPAT_SCALE_MODE_SYSTEM_LAST);
+ }
+
+ /**
+ * @return an instance of {@link CompatScale} to apply for the given package
+ */
+ @Nullable
+ CompatScale getCompatScale(@NonNull String packageName, int uid);
+}
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 5aa7c97..7cd07d6 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -35,6 +35,7 @@
import android.os.ServiceManager;
import android.provider.DeviceConfig;
import android.view.ContentRecordingSession;
+import android.view.ContentRecordingSession.RecordContent;
import android.view.Display;
import android.view.SurfaceControl;
@@ -84,6 +85,7 @@
/**
* The last configuration orientation.
*/
+ @Configuration.Orientation
private int mLastOrientation = ORIENTATION_UNDEFINED;
ContentRecorder(@NonNull DisplayContent displayContent) {
@@ -149,6 +151,20 @@
return;
}
+ // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are
+ // inaccurate.
+ if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
+ final Task capturedTask = mRecordedWindowContainer.asTask();
+ if (capturedTask.inPinnedWindowingMode()) {
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: Display %d was already recording, but "
+ + "pause capture since the task is in PIP",
+ mDisplayContent.getDisplayId());
+ pauseRecording();
+ return;
+ }
+ }
+
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
"Content Recording: Display %d was already recording, so apply "
+ "transformations if necessary",
@@ -156,7 +172,8 @@
// Retrieve the size of the region to record, and continue with the update
// if the bounds or orientation has changed.
final Rect recordedContentBounds = mRecordedWindowContainer.getBounds();
- int recordedContentOrientation = mRecordedWindowContainer.getOrientation();
+ @Configuration.Orientation int recordedContentOrientation =
+ mRecordedWindowContainer.getConfiguration().orientation;
if (!mLastRecordedBounds.equals(recordedContentBounds)
|| lastOrientation != recordedContentOrientation) {
Point surfaceSize = fetchSurfaceSizeIfPresent();
@@ -289,6 +306,17 @@
return;
}
+ // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are inaccurate.
+ if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
+ if (mRecordedWindowContainer.asTask().inPinnedWindowingMode()) {
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: Display %d should start recording, but "
+ + "don't yet since the task is in PIP",
+ mDisplayContent.getDisplayId());
+ return;
+ }
+ }
+
final Point surfaceSize = fetchSurfaceSizeIfPresent();
if (surfaceSize == null) {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
@@ -302,9 +330,6 @@
+ "state %d",
mDisplayContent.getDisplayId(), mDisplayContent.getDisplayInfo().state);
- // TODO(b/274790702): Do not start recording if waiting for consent - for now,
- // go ahead.
-
// Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture.
mRecordedSurface = SurfaceControl.mirrorSurface(
mRecordedWindowContainer.getSurfaceControl());
@@ -356,7 +381,7 @@
*/
@Nullable
private WindowContainer retrieveRecordedWindowContainer() {
- final int contentToRecord = mContentRecordingSession.getContentToRecord();
+ @RecordContent final int contentToRecord = mContentRecordingSession.getContentToRecord();
final IBinder tokenToRecord = mContentRecordingSession.getTokenToRecord();
switch (contentToRecord) {
case RECORD_CONTENT_DISPLAY:
@@ -472,6 +497,12 @@
shiftedY = (surfaceSize.y - scaledHeight) / 2;
}
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: Apply transformations of shift %d x %d, scale %f, crop %d x "
+ + "%d for display %d",
+ shiftedX, shiftedY, scale, recordedContentBounds.width(),
+ recordedContentBounds.height(), mDisplayContent.getDisplayId());
+
transaction
// Crop the area to capture to exclude the 'extra' wallpaper that is used
// for parallax (b/189930234).
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 707b779..395ab3a 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -269,6 +269,8 @@
private boolean mIsFreeformWindowOverlappingWithNavBar;
+ private @InsetsType int mForciblyShownTypes;
+
private boolean mIsImmersiveMode;
// The windows we were told about in focusChanged.
@@ -1402,6 +1404,7 @@
mAllowLockscreenWhenOn = false;
mShowingDream = false;
mIsFreeformWindowOverlappingWithNavBar = false;
+ mForciblyShownTypes = 0;
}
/**
@@ -1459,6 +1462,10 @@
}
}
+ if (win.mSession.mCanForceShowingInsets) {
+ mForciblyShownTypes |= win.mAttrs.forciblyShownTypes;
+ }
+
if (!affectsSystemUi) {
return;
}
@@ -1640,6 +1647,10 @@
mService.mPolicy.setAllowLockscreenWhenOn(getDisplayId(), mAllowLockscreenWhenOn);
}
+ boolean areTypesForciblyShownTransiently(@InsetsType int types) {
+ return (mForciblyShownTypes & types) == types;
+ }
+
/**
* Applies the keyguard policy to a specific window.
*
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 835c92d..d0d7f49 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -23,8 +23,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.InsetsSource.ID_IME;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import android.annotation.NonNull;
@@ -77,6 +75,18 @@
/** Used to show system bars permanently. This will affect the layout. */
private final InsetsControlTarget mPermanentControlTarget;
+ /**
+ * Used to override the visibility of {@link Type#statusBars()} when dispatching insets to
+ * clients.
+ */
+ private InsetsControlTarget mFakeStatusControlTarget;
+
+ /**
+ * Used to override the visibility of {@link Type#navigationBars()} when dispatching insets to
+ * clients.
+ */
+ private InsetsControlTarget mFakeNavControlTarget;
+
private WindowState mFocusedWin;
private final BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
private final BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR);
@@ -103,25 +113,25 @@
abortTransient();
}
mFocusedWin = focusedWin;
- final InsetsControlTarget statusControlTarget =
- getStatusControlTarget(focusedWin, false /* fake */);
- final InsetsControlTarget navControlTarget =
- getNavControlTarget(focusedWin, false /* fake */);
final WindowState notificationShade = mPolicy.getNotificationShade();
final WindowState topApp = mPolicy.getTopFullscreenOpaqueWindow();
+ final InsetsControlTarget statusControlTarget =
+ getStatusControlTarget(focusedWin, false /* fake */);
+ mFakeStatusControlTarget = statusControlTarget == mTransientControlTarget
+ ? getStatusControlTarget(focusedWin, true /* fake */)
+ : statusControlTarget == notificationShade
+ ? getStatusControlTarget(topApp, true /* fake */)
+ : null;
+ final InsetsControlTarget navControlTarget =
+ getNavControlTarget(focusedWin, false /* fake */);
+ mFakeNavControlTarget = navControlTarget == mTransientControlTarget
+ ? getNavControlTarget(focusedWin, true /* fake */)
+ : navControlTarget == notificationShade
+ ? getNavControlTarget(topApp, true /* fake */)
+ : null;
mStateController.onBarControlTargetChanged(
- statusControlTarget,
- statusControlTarget == mTransientControlTarget
- ? getStatusControlTarget(focusedWin, true /* fake */)
- : statusControlTarget == notificationShade
- ? getStatusControlTarget(topApp, true /* fake */)
- : null,
- navControlTarget,
- navControlTarget == mTransientControlTarget
- ? getNavControlTarget(focusedWin, true /* fake */)
- : navControlTarget == notificationShade
- ? getNavControlTarget(topApp, true /* fake */)
- : null);
+ statusControlTarget, mFakeStatusControlTarget,
+ navControlTarget, mFakeNavControlTarget);
mStatusBar.updateVisibility(statusControlTarget, Type.statusBars());
mNavBar.updateVisibility(navControlTarget, Type.navigationBars());
}
@@ -206,7 +216,7 @@
boolean includesTransient) {
InsetsState state;
if (!includesTransient) {
- state = adjustVisibilityForTransientTypes(originalState);
+ state = adjustVisibilityForFakeControllingSources(originalState);
} else {
state = originalState;
}
@@ -321,24 +331,40 @@
return state;
}
- private InsetsState adjustVisibilityForTransientTypes(InsetsState originalState) {
+ private InsetsState adjustVisibilityForFakeControllingSources(InsetsState originalState) {
+ if (mFakeStatusControlTarget == null && mFakeNavControlTarget == null) {
+ return originalState;
+ }
InsetsState state = originalState;
for (int i = state.sourceSize() - 1; i >= 0; i--) {
final InsetsSource source = state.sourceAt(i);
- if (isTransient(source.getType()) && source.isVisible()) {
- if (state == originalState) {
- // The source will be modified, create a non-deep copy to store the new one.
- state = new InsetsState(originalState);
- }
- // Replace the source with a copy in invisible state.
- final InsetsSource outSource = new InsetsSource(source);
- outSource.setVisible(false);
- state.addSource(outSource);
- }
+ state = adjustVisibilityForFakeControllingSource(state, Type.statusBars(), source,
+ mFakeStatusControlTarget);
+ state = adjustVisibilityForFakeControllingSource(state, Type.navigationBars(), source,
+ mFakeNavControlTarget);
}
return state;
}
+ private static InsetsState adjustVisibilityForFakeControllingSource(InsetsState originalState,
+ @InsetsType int type, InsetsSource source, InsetsControlTarget target) {
+ if (source.getType() != type || target == null) {
+ return originalState;
+ }
+ final boolean isRequestedVisible = target.isRequestedVisible(type);
+ if (source.isVisible() == isRequestedVisible) {
+ return originalState;
+ }
+ // The source will be modified, create a non-deep copy to store the new one.
+ final InsetsState state = new InsetsState(originalState);
+
+ // Replace the source with a copy with the overridden visibility.
+ final InsetsSource outSource = new InsetsSource(source);
+ outSource.setVisible(isRequestedVisible);
+ state.addSource(outSource);
+ return state;
+ }
+
private InsetsState adjustVisibilityForIme(WindowState w, InsetsState originalState,
boolean copyState) {
if (w.mIsImWindow) {
@@ -473,7 +499,7 @@
// we will dispatch the real visibility of status bar to the client.
return mPermanentControlTarget;
}
- if (forceShowsStatusBarTransiently() && !fake) {
+ if (mPolicy.areTypesForciblyShownTransiently(Type.statusBars()) && !fake) {
// Status bar is forcibly shown transiently, and its new visibility won't be
// dispatched to the client so that we can keep the layout stable. We will dispatch the
// fake control to the client, so that it can re-show the bar during this scenario.
@@ -505,7 +531,7 @@
if (imeWin != null && imeWin.isVisible() && !mHideNavBarForKeyboard) {
// Force showing navigation bar while IME is visible and if navigation bar is not
// configured to be hidden by the IME.
- return null;
+ return mPermanentControlTarget;
}
if (!fake && isTransient(Type.navigationBars())) {
return mTransientControlTarget;
@@ -533,7 +559,7 @@
// bar, and we will dispatch the real visibility of navigation bar to the client.
return mPermanentControlTarget;
}
- if (forceShowsNavigationBarTransiently() && !fake) {
+ if (mPolicy.areTypesForciblyShownTransiently(Type.navigationBars()) && !fake) {
// Navigation bar is forcibly shown transiently, and its new visibility won't be
// dispatched to the client so that we can keep the layout stable. We will dispatch the
// fake control to the client, so that it can re-show the bar during this scenario.
@@ -603,17 +629,6 @@
&& focusedWin.getAttrs().type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
}
- private boolean forceShowsStatusBarTransiently() {
- final WindowState win = mPolicy.getStatusBar();
- return win != null && (win.mAttrs.privateFlags & PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR) != 0;
- }
-
- private boolean forceShowsNavigationBarTransiently() {
- final WindowState win = mPolicy.getNotificationShade();
- return win != null
- && (win.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
- }
-
private void dispatchTransientSystemBarsVisibilityChanged(
@Nullable WindowState focusedWindow,
boolean areVisible,
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 26abe51..458786f 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -18,4 +18,4 @@
yunfanc@google.com
per-file BackgroundActivityStartController.java = set noparent
-per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
+per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/PendingRemoteAnimationRegistry.java b/services/core/java/com/android/server/wm/PendingRemoteAnimationRegistry.java
index 073bbbb..aa04aec 100644
--- a/services/core/java/com/android/server/wm/PendingRemoteAnimationRegistry.java
+++ b/services/core/java/com/android/server/wm/PendingRemoteAnimationRegistry.java
@@ -30,7 +30,7 @@
*/
class PendingRemoteAnimationRegistry {
- private static final long TIMEOUT_MS = 3000;
+ static final long TIMEOUT_MS = 3000;
private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
private final Handler mHandler;
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index f33ecaa..184de58 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -301,9 +301,12 @@
}
// Always update the reordering time when this is called to ensure that the timeout
- // is reset
+ // is reset. Extend this duration when running in tests.
+ final long timeout = ActivityManager.isRunningInUserTestHarness()
+ ? mFreezeTaskListTimeoutMs * 10
+ : mFreezeTaskListTimeoutMs;
mService.mH.removeCallbacks(mResetFreezeTaskListOnTimeoutRunnable);
- mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, mFreezeTaskListTimeoutMs);
+ mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, timeout);
}
/**
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index fe3094e..6418148 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -148,7 +148,8 @@
.setPendingIntentBackgroundActivityStartMode(
options.getPendingIntentBackgroundActivityStartMode())
.setPendingIntentCreatorBackgroundActivityStartMode(
- options.getPendingIntentCreatorBackgroundActivityStartMode());
+ options.getPendingIntentCreatorBackgroundActivityStartMode())
+ .setRemoteTransition(options.getRemoteTransition());
}
/**
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1845ae8..0674ec1 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -23,6 +23,7 @@
import static android.Manifest.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION;
import static android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.Manifest.permission.STATUS_BAR_SERVICE;
import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
@@ -108,6 +109,7 @@
private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>();
private final DragDropController mDragDropController;
final boolean mCanAddInternalSystemWindow;
+ boolean mCanForceShowingInsets;
private final boolean mCanStartTasksFromRecents;
final boolean mCanCreateSystemApplicationOverlay;
@@ -131,6 +133,9 @@
mLastReportedAnimatorScale = service.getCurrentAnimatorScale();
mCanAddInternalSystemWindow = service.mContext.checkCallingOrSelfPermission(
INTERNAL_SYSTEM_WINDOW) == PERMISSION_GRANTED;
+ mCanForceShowingInsets = service.mAtmService.isCallerRecents(mUid)
+ || service.mContext.checkCallingOrSelfPermission(STATUS_BAR_SERVICE)
+ == PERMISSION_GRANTED;
mCanHideNonSystemOverlayWindows = service.mContext.checkCallingOrSelfPermission(
HIDE_NON_SYSTEM_OVERLAY_WINDOWS) == PERMISSION_GRANTED
|| service.mContext.checkCallingOrSelfPermission(HIDE_OVERLAY_WINDOWS)
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index e4b9571..0d78701 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -1147,7 +1147,7 @@
Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */);
} else if (!animatingState && mAnimatingState) {
t.setEarlyWakeupEnd();
- mAtm.mWindowManager.requestTraversal();
+ mAtm.mWindowManager.scheduleAnimationLocked();
mSnapshotController.setPause(false);
mAnimatingState = false;
Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */);
diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp
index d4d17ec..41706f0 100644
--- a/services/devicepolicy/Android.bp
+++ b/services/devicepolicy/Android.bp
@@ -24,4 +24,7 @@
"app-compat-annotations",
"service-permission.stubs.system_server",
],
+ static_libs: [
+ "device_policy_aconfig_flags_lib",
+ ],
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 9c1d765..21b1291 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -624,6 +624,27 @@
}
/**
+ * Retrieves the global policy set by the admin for the provided {@code policyDefinition}
+ * if one was set, otherwise returns {@code null}.
+ */
+ @Nullable
+ <V> V getGlobalPolicySetByAdmin(
+ @NonNull PolicyDefinition<V> policyDefinition,
+ @NonNull EnforcingAdmin enforcingAdmin) {
+ Objects.requireNonNull(policyDefinition);
+ Objects.requireNonNull(enforcingAdmin);
+
+ synchronized (mLock) {
+ if (!hasGlobalPolicyLocked(policyDefinition)) {
+ return null;
+ }
+ PolicyValue<V> value = getGlobalPolicyStateLocked(policyDefinition)
+ .getPoliciesSetByAdmins().get(enforcingAdmin);
+ return value == null ? null : value.getValue();
+ }
+ }
+
+ /**
* Retrieves the values set for the provided {@code policyDefinition} by each admin.
*/
@NonNull
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 438a9d6..50dc061 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -491,6 +491,7 @@
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;
+import com.android.server.devicepolicy.flags.FlagUtils;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.DefaultCrossProfileIntentFilter;
@@ -3432,6 +3433,9 @@
}
revertTransferOwnershipIfNecessaryLocked();
+ if (!FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+ updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked());
+ }
}
// In case flag value has changed, we apply it during boot to avoid doing it concurrently
@@ -9121,9 +9125,15 @@
UserManager.DISALLOW_CAMERA);
if (who != null) {
EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackageName);
- return Boolean.TRUE.equals(
- mDevicePolicyEngine.getLocalPolicySetByAdmin(
- policy, admin, affectedUserId));
+ Boolean value = null;
+ if (isDeviceOwner(caller)) {
+ value = mDevicePolicyEngine.getGlobalPolicySetByAdmin(policy, admin);
+ } else {
+ value = mDevicePolicyEngine.getLocalPolicySetByAdmin(
+ policy, admin, affectedUserId);
+ }
+ return Boolean.TRUE.equals(value);
+
} else {
return Boolean.TRUE.equals(
mDevicePolicyEngine.getResolvedPolicy(policy, affectedUserId));
@@ -21575,17 +21585,35 @@
Objects.requireNonNull(packageName, "Admin package name must be provided");
final CallerIdentity caller = getCallerIdentity(packageName);
- synchronized (getLockObject()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- /* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
- caller.getPackageName(),
- caller.getUserId());
+ if (!FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ "USB data signaling can only be controlled by a device owner or "
+ + "a profile owner on an organization-owned device.");
Preconditions.checkState(canUsbDataSignalingBeDisabled(),
"USB data signaling cannot be disabled.");
- mDevicePolicyEngine.setGlobalPolicy(
- PolicyDefinition.USB_DATA_SIGNALING,
- enforcingAdmin,
- new BooleanPolicyValue(enabled));
+ }
+
+ synchronized (getLockObject()) {
+ if (FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+ EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ /* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
+ caller.getPackageName(),
+ caller.getUserId());
+ Preconditions.checkState(canUsbDataSignalingBeDisabled(),
+ "USB data signaling cannot be disabled.");
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.USB_DATA_SIGNALING,
+ enforcingAdmin,
+ new BooleanPolicyValue(enabled));
+ } else {
+ ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
+ if (admin.mUsbDataSignalingEnabled != enabled) {
+ admin.mUsbDataSignalingEnabled = enabled;
+ saveSettingsLocked(caller.getUserId());
+ updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked());
+ }
+ }
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_USB_DATA_SIGNALING)
@@ -21607,10 +21635,24 @@
@Override
public boolean isUsbDataSignalingEnabled(String packageName) {
final CallerIdentity caller = getCallerIdentity(packageName);
- Boolean enabled = mDevicePolicyEngine.getResolvedPolicy(
- PolicyDefinition.USB_DATA_SIGNALING,
- caller.getUserId());
- return enabled == null || enabled;
+ if (FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+ Boolean enabled = mDevicePolicyEngine.getResolvedPolicy(
+ PolicyDefinition.USB_DATA_SIGNALING,
+ caller.getUserId());
+ return enabled == null || enabled;
+ } else {
+ synchronized (getLockObject()) {
+ // If the caller is an admin, return the policy set by itself. Otherwise
+ // return the device-wide policy.
+ if (isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(
+ caller)) {
+ return getProfileOwnerOrDeviceOwnerLocked(
+ caller.getUserId()).mUsbDataSignalingEnabled;
+ } else {
+ return isUsbDataSignalingEnabledInternalLocked();
+ }
+ }
+ }
}
private boolean isUsbDataSignalingEnabledInternalLocked() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index 599c4a7..22464d5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -260,11 +260,14 @@
break;
}
}
- if (admin != null) {
+ if (admin != null && value != null) {
policiesSetByAdmins.put(admin, value);
} else {
- Slogf.wtf(TAG, "Error Parsing TAG_ADMIN_POLICY_ENTRY, EnforcingAdmin "
- + "is null");
+ Slogf.wtf(TAG, "Error Parsing TAG_ADMIN_POLICY_ENTRY for "
+ + (policyDefinition == null ? "unknown policy" : "policy with "
+ + "definition " + policyDefinition) + ", EnforcingAdmin is: "
+ + (admin == null ? "null" : admin) + ", value is : "
+ + (value == null ? "null" : value));
}
break;
case TAG_POLICY_DEFINITION_ENTRY:
@@ -283,7 +286,9 @@
}
currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(parser);
if (currentResolvedPolicy == null) {
- Slogf.wtf(TAG, "Error Parsing TAG_RESOLVED_VALUE_ENTRY, "
+ Slogf.wtf(TAG, "Error Parsing TAG_RESOLVED_VALUE_ENTRY for "
+ + (policyDefinition == null ? "unknown policy" : "policy with "
+ + "definition " + policyDefinition) + ", "
+ "currentResolvedPolicy is null");
}
break;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp b/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
new file mode 100644
index 0000000..1a45782
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
@@ -0,0 +1,16 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aconfig_declarations {
+ name: "device_policy_aconfig_flags",
+ package: "com.android.server.devicepolicy.flags",
+ srcs: [
+ "flags.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "device_policy_aconfig_flags_lib",
+ aconfig_declarations: "device_policy_aconfig_flags",
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
new file mode 100644
index 0000000..9fe3749
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy.flags;
+
+import static com.android.server.devicepolicy.flags.Flags.policyEngineMigrationV2Enabled;
+
+import android.os.Binder;
+
+public final class FlagUtils {
+ private FlagUtils(){}
+
+ public static boolean isPolicyEngineMigrationV2Enabled() {
+ return Binder.withCleanCallingIdentity(() -> {
+ return policyEngineMigrationV2Enabled();
+ });
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
new file mode 100644
index 0000000..00702a9
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.devicepolicy.flags"
+
+flag {
+ name: "policy_engine_migration_v2_enabled"
+ namespace: "enterprise"
+ description: "V2 of the policy engine migrations for Android V"
+ bug: "289520697"
+}
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
index e87a34e..798e1ae 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
@@ -158,7 +158,7 @@
assertThat(result.result).isEqualTo(InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING);
assertThat(result.id).isEqualTo(info.getId());
synchronized (ImfLock.class) {
- assertThat(mBindingController.hasConnection()).isTrue();
+ assertThat(mBindingController.hasMainConnection()).isTrue();
assertThat(mBindingController.getCurId()).isEqualTo(info.getId());
assertThat(mBindingController.getCurToken()).isNotNull();
}
@@ -202,7 +202,7 @@
synchronized (ImfLock.class) {
// Unbind both main connection and visible connection
- assertThat(mBindingController.hasConnection()).isFalse();
+ assertThat(mBindingController.hasMainConnection()).isFalse();
assertThat(mBindingController.isVisibleBound()).isFalse();
verify(mContext, times(2)).unbindService(any(ServiceConnection.class));
assertThat(mBindingController.getCurToken()).isNull();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
similarity index 92%
rename from services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
rename to services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 60b28d3..d988063 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -38,7 +38,6 @@
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
-import android.content.pm.PackageArchiver;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
@@ -75,7 +74,7 @@
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class PackageArchiverServiceTest {
+public class PackageArchiverTest {
private static final String PACKAGE = "com.example";
private static final String CALLER_PACKAGE = "com.caller";
@@ -119,7 +118,7 @@
private PackageSetting mPackageSetting;
- private PackageArchiverService mArchiveService;
+ private PackageArchiver mArchiveManager;
@Before
public void setUp() throws Exception {
@@ -160,8 +159,8 @@
mock(Resources.class));
when(mIcon.compress(eq(Bitmap.CompressFormat.PNG), eq(100), any())).thenReturn(true);
- mArchiveService = spy(new PackageArchiverService(mContext, pm));
- doReturn(ICON_PATH).when(mArchiveService).storeIcon(eq(PACKAGE),
+ mArchiveManager = spy(new PackageArchiver(mContext, pm));
+ doReturn(ICON_PATH).when(mArchiveManager).storeIcon(eq(PACKAGE),
any(LauncherActivityInfo.class), eq(mUserId));
}
@@ -169,7 +168,7 @@
public void archiveApp_callerPackageNameIncorrect() {
Exception e = assertThrows(
SecurityException.class,
- () -> mArchiveService.requestArchive(PACKAGE, "different", mIntentSender,
+ () -> mArchiveManager.requestArchive(PACKAGE, "different", mIntentSender,
UserHandle.CURRENT));
assertThat(e).hasMessageThat().isEqualTo(
String.format(
@@ -186,7 +185,7 @@
Exception e = assertThrows(
ParcelableException.class,
- () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
@@ -197,7 +196,7 @@
public void archiveApp_packageNotInstalledForUser() throws IntentSender.SendIntentException {
mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);
- mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
+ mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -226,7 +225,7 @@
Exception e = assertThrows(
ParcelableException.class,
- () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo("No installer found");
@@ -239,7 +238,7 @@
Exception e = assertThrows(
ParcelableException.class,
- () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
@@ -249,10 +248,10 @@
@Test
public void archiveApp_storeIconFails() throws IntentSender.SendIntentException, IOException {
IOException e = new IOException("IO");
- doThrow(e).when(mArchiveService).storeIcon(eq(PACKAGE),
+ doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE),
any(LauncherActivityInfo.class), eq(mUserId));
- mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
+ mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -268,7 +267,7 @@
@Test
public void archiveApp_success() {
- mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
+ mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
verify(mInstallerService).uninstall(
@@ -286,7 +285,7 @@
Exception e = assertThrows(
SecurityException.class,
- () -> mArchiveService.requestUnarchive(PACKAGE, "different",
+ () -> mArchiveManager.requestUnarchive(PACKAGE, "different",
UserHandle.CURRENT));
assertThat(e).hasMessageThat().isEqualTo(
String.format(
@@ -304,7 +303,7 @@
Exception e = assertThrows(
ParcelableException.class,
- () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+ () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
@@ -315,7 +314,7 @@
public void unarchiveApp_notArchived() {
Exception e = assertThrows(
ParcelableException.class,
- () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+ () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
@@ -338,7 +337,7 @@
Exception e = assertThrows(
ParcelableException.class,
- () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+ () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
@@ -349,7 +348,7 @@
public void unarchiveApp_success() {
mUserState.setArchiveState(createArchiveState()).setInstalled(false);
- mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT);
+ mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -366,10 +365,10 @@
/* initialExtras= */ isNull());
Intent intent = intentCaptor.getValue();
assertThat(intent.getFlags() & FLAG_RECEIVER_FOREGROUND).isNotEqualTo(0);
- assertThat(intent.getStringExtra(PackageArchiver.EXTRA_UNARCHIVE_PACKAGE_NAME)).isEqualTo(
+ assertThat(intent.getStringExtra(PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME)).isEqualTo(
PACKAGE);
assertThat(
- intent.getBooleanExtra(PackageArchiver.EXTRA_UNARCHIVE_ALL_USERS, true)).isFalse();
+ intent.getBooleanExtra(PackageInstaller.EXTRA_UNARCHIVE_ALL_USERS, true)).isFalse();
assertThat(intent.getPackage()).isEqualTo(INSTALLER_PACKAGE);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
index 94fff22..a3917765 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
@@ -55,6 +55,7 @@
@Mock
private InternalResourceService mIrs;
+ private Agent mAgent;
private Scribe mScribe;
private static class MockScribe extends Scribe {
@@ -80,10 +81,13 @@
doReturn(mIrs).when(mIrs).getLock();
doReturn(mock(AlarmManager.class)).when(mContext).getSystemService(Context.ALARM_SERVICE);
mScribe = new MockScribe(mIrs, mAnalyst);
+ mAgent = new Agent(mIrs, mScribe, mAnalyst);
}
@After
public void tearDown() {
+ mAgent.tearDownLocked();
+
if (mMockingSession != null) {
mMockingSession.finishMocking();
}
@@ -99,7 +103,6 @@
final int userId = 0;
final String pkgName = "com.test";
- final Agent agent = new Agent(mIrs, mScribe, mAnalyst);
final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
doReturn(consumptionLimit).when(mIrs).getConsumptionLimitLocked();
@@ -107,66 +110,64 @@
.getMaxSatiatedBalance(anyInt(), anyString());
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 10);
- agent.recordTransactionLocked(userId, pkgName, ledger, transaction, false);
+ mAgent.recordTransactionLocked(userId, pkgName, ledger, transaction, false);
assertEquals(5, ledger.getCurrentBalance());
assertEquals(remainingCakes - 10, mScribe.getRemainingConsumableCakesLocked());
- agent.onPackageRemovedLocked(userId, pkgName);
+ mAgent.onPackageRemovedLocked(userId, pkgName);
assertEquals(remainingCakes - 10, mScribe.getRemainingConsumableCakesLocked());
assertLedgersEqual(new Ledger(), mScribe.getLedgerLocked(userId, pkgName));
}
@Test
public void testRecordTransaction_UnderMax() {
- Agent agent = new Agent(mIrs, mScribe, mAnalyst);
Ledger ledger = new Ledger();
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(5, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(500, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L, 500);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1_000_000L, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -1_000_001L, 1000);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(-1, ledger.getCurrentBalance());
}
@Test
public void testRecordTransaction_MaxConsumptionLimit() {
- Agent agent = new Agent(mIrs, mScribe, mAnalyst);
Ledger ledger = new Ledger();
doReturn(1000L).when(mIrs).getConsumptionLimitLocked();
doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(5, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(500, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 2000, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(2500, ledger.getCurrentBalance());
// ConsumptionLimit can change as the battery level changes. Ledger balances shouldn't be
@@ -174,57 +175,56 @@
doReturn(900L).when(mIrs).getConsumptionLimitLocked();
transaction = new Ledger.Transaction(0, 0, 0, null, 100, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(2600, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -50, 50);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(2550, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -200, 100);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(2350, ledger.getCurrentBalance());
doReturn(800L).when(mIrs).getConsumptionLimitLocked();
transaction = new Ledger.Transaction(0, 0, 0, null, 100, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(2450, ledger.getCurrentBalance());
}
@Test
public void testRecordTransaction_MaxSatiatedBalance() {
- Agent agent = new Agent(mIrs, mScribe, mAnalyst);
Ledger ledger = new Ledger();
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(5, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(500, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L, 1000);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1_000, ledger.getCurrentBalance());
// Shouldn't change in normal operation, but adding test case in case it does.
doReturn(900L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
transaction = new Ledger.Transaction(0, 0, 0, null, 500, 0);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(1_000, ledger.getCurrentBalance());
transaction = new Ledger.Transaction(0, 0, 0, null, -1001, 500);
- agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+ mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
assertEquals(-1, ledger.getCurrentBalance());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
index 035bef6..3808f30 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
@@ -47,7 +47,9 @@
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.ArraySet;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -90,8 +92,7 @@
private static final int STREAMED_CALLING_UID = 9876;
@Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock private Context mMockContext;
@Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
@@ -116,8 +117,6 @@
MockitoAnnotations.initMocks(this);
final Resources resources = InstrumentationRegistry.getContext().getResources();
- mSetFlagsRule.enableFlags(Flags.FLAG_PROXY_USE_APPS_ON_VIRTUAL_DEVICE_LISTENER);
-
mFocusStrokeWidthDefaultValue =
resources.getDimensionPixelSize(R.dimen.accessibility_focus_highlight_stroke_width);
mFocusColorDefaultValue = resources.getColor(R.color.accessibility_focus_highlight_color);
@@ -230,6 +229,7 @@
* app changes to the proxy device.
*/
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_PROXY_USE_APPS_ON_VIRTUAL_DEVICE_LISTENER)
public void testUpdateProxyOfRunningAppsChange_changedUidIsStreamedApp_propagatesChange() {
final VirtualDeviceManagerInternal localVdm =
Mockito.mock(VirtualDeviceManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 78655a5..c40ad28 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -79,9 +79,9 @@
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
/* allowedUsers= */ new ArraySet<>(),
/* activityLaunchAllowedByDefault= */ true,
- /* activityPolicyExceptions= */ new ArraySet<>(),
+ /* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
- /* crossTaskNavigationExceptions= */ new ArraySet<>(),
+ /* crossTaskNavigationExemptions= */ new ArraySet<>(),
/* activityListener= */ null,
/* pipBlockedCallback= */ null,
/* activityBlockedCallback= */ null,
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 2273fcd..9f75cf8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -113,6 +113,42 @@
assertUserInfoEquals(data.info, read.info, /* parcelCopy= */ false);
}
+ /** Tests that device policy restrictions are written/read properly. */
+ @Test
+ public void testWriteReadDevicePolicyUserRestrictions() throws Exception {
+ final String globalRestriction = UserManager.DISALLOW_FACTORY_RESET;
+ final String localRestriction = UserManager.DISALLOW_CONFIG_DATE_TIME;
+
+ UserData data = new UserData();
+ data.info = createUser(100, FLAG_FULL, "A type");
+
+ mUserManagerService.putUserInfo(data.info);
+
+ // Set a global and user restriction so they get written out to the user file.
+ setUserRestrictions(data.info.id, globalRestriction, localRestriction, true);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(baos);
+ mUserManagerService.writeUserLP(data, out);
+ byte[] bytes = baos.toByteArray();
+
+ // Clear the restrictions to see if they are properly read in from the user file.
+ setUserRestrictions(data.info.id, globalRestriction, localRestriction, false);
+
+ mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(bytes));
+ assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(globalRestriction));
+ assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(localRestriction));
+ }
+
+ /** Sets a global and local restriction and verifies they were set properly **/
+ private void setUserRestrictions(int id, String global, String local, boolean enabled) {
+ mUserManagerService.setUserRestrictionInner(UserHandle.USER_ALL, global, enabled);
+ assertEquals(mUserManagerService.hasUserRestrictionOnAnyUser(global), enabled);
+
+ mUserManagerService.setUserRestrictionInner(id, local, enabled);
+ assertEquals(mUserManagerService.hasUserRestrictionOnAnyUser(local), enabled);
+ }
+
@Test
public void testParcelUnparcelUserInfo() throws Exception {
UserInfo info = createUser();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index ae87e38..e2bb115 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -174,7 +174,7 @@
mController = mock(ActivityStartController.class);
BackgroundActivityStartController balController =
new BackgroundActivityStartController(mAtm, mSupervisor);
- doReturn(balController).when(mController).getBackgroundActivityLaunchController();
+ doReturn(balController).when(mAtm.mTaskSupervisor).getBackgroundActivityLaunchController();
mActivityMetricsLogger = mock(ActivityMetricsLogger.class);
clearInvocations(mActivityMetricsLogger);
mAppOpsManager = mAtm.getAppOpsManager();
diff --git a/services/tests/wmtests/src/com/android/server/wm/CompatScaleProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/CompatScaleProviderTest.java
new file mode 100644
index 0000000..96e3cb1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/CompatScaleProviderTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+
+/**
+ * Tests for the {@link CompatScaleProvider} interface.
+ * See {@link CompatModePackages} class for implementation.
+ *
+ * Build/Install/Run:
+ * atest WmTests:CompatScaleProviderTest
+ */
+@SmallTest
+@Presubmit
+public class CompatScaleProviderTest extends SystemServiceTestsBase {
+ private static final String TEST_PACKAGE = "compat.mode.packages";
+ static final int TEST_USER_ID = 1;
+
+ private ActivityTaskManagerService mAtm;
+
+ /**
+ * setup method before every test.
+ */
+ @Before
+ public void setUp() {
+ mAtm = mSystemServicesTestRule.getActivityTaskManagerService();
+ }
+
+ /**
+ * Registering a {@link CompatScaleProvider} with an invalid id should throw an exception.
+ */
+ @Test
+ public void registerCompatScaleProviderWithInvalidId() {
+ CompatScaleProvider compatScaleProvider = mock(CompatScaleProvider.class);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mAtm.registerCompatScaleProvider(-1, compatScaleProvider)
+ );
+ }
+
+ /**
+ * Registering a {@code null} {@link CompatScaleProvider} should throw an exception.
+ */
+ @Test
+ public void registerCompatScaleProviderFailIfCallbackIsNull() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mAtm.registerCompatScaleProvider(
+ CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT, null)
+ );
+ }
+
+ /**
+ * Registering a {@link CompatScaleProvider} with a already registered id should throw an
+ * exception.
+ */
+ @Test
+ public void registerCompatScaleProviderFailIfIdIsAlreadyRegistered() {
+ CompatScaleProvider compatScaleProvider = mock(CompatScaleProvider.class);
+ mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT,
+ compatScaleProvider);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mAtm.registerCompatScaleProvider(
+ CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT, compatScaleProvider)
+ );
+ mAtm.unregisterCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT);
+ }
+
+ /**
+ * Successfully registering a {@link CompatScaleProvider} with should result in callbacks
+ * getting called.
+ */
+ @Test
+ public void registerCompatScaleProviderSuccessfully() {
+ CompatScaleProvider compatScaleProvider = mock(CompatScaleProvider.class);
+ mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT,
+ compatScaleProvider);
+ mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+ verify(compatScaleProvider, times(1)).getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+ mAtm.unregisterCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT);
+ }
+
+ /**
+ * Unregistering a {@link CompatScaleProvider} with a unregistered id should throw an exception.
+ */
+ @Test
+ public void unregisterCompatScaleProviderFailIfIdNotRegistered() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mAtm.unregisterCompatScaleProvider(
+ CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT)
+ );
+ }
+
+ /**
+ * Unregistering a {@link CompatScaleProvider} with an invalid id should throw an exception.
+ */
+ @Test
+ public void unregisterCompatScaleProviderFailIfIdNotInRange() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mAtm.unregisterCompatScaleProvider(-1)
+ );
+ }
+
+ /**
+ * Successfully unregistering a {@link CompatScaleProvider} should stop the callbacks from
+ * getting called.
+ */
+ @Test
+ public void unregisterCompatScaleProviderSuccessfully() {
+ CompatScaleProvider compatScaleProvider = mock(CompatScaleProvider.class);
+ mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT,
+ compatScaleProvider);
+ mAtm.unregisterCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT);
+ mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+ verify(compatScaleProvider, never()).getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+ }
+
+ /**
+ * Order of calling {@link CompatScaleProvider} is same as the id that was used for
+ * registering it.
+ */
+ @Test
+ public void registerCompatScaleProviderRespectsOrderId() {
+ CompatScaleProvider gameModeCompatScaleProvider = mock(CompatScaleProvider.class);
+ CompatScaleProvider productCompatScaleProvider = mock(CompatScaleProvider.class);
+ mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_GAME,
+ gameModeCompatScaleProvider);
+ mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT,
+ productCompatScaleProvider);
+ mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+ InOrder inOrder = inOrder(gameModeCompatScaleProvider, productCompatScaleProvider);
+ inOrder.verify(gameModeCompatScaleProvider).getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+ inOrder.verify(productCompatScaleProvider).getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index c84eab3..ecd84e1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -16,6 +16,11 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
@@ -77,6 +82,7 @@
@RunWith(WindowTestRunner.class)
public class ContentRecorderTests extends WindowTestsBase {
private static IBinder sTaskWindowContainerToken;
+ private DisplayContent mVirtualDisplayContent;
private Task mTask;
private final ContentRecordingSession mDisplaySession =
ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
@@ -107,11 +113,11 @@
displayInfo.logicalWidth = sSurfaceSize.x;
displayInfo.logicalHeight = sSurfaceSize.y;
displayInfo.state = STATE_ON;
- final DisplayContent virtualDisplayContent = createNewDisplay(displayInfo);
- final int displayId = virtualDisplayContent.getDisplayId();
- mContentRecorder = new ContentRecorder(virtualDisplayContent,
+ mVirtualDisplayContent = createNewDisplay(displayInfo);
+ final int displayId = mVirtualDisplayContent.getDisplayId();
+ mContentRecorder = new ContentRecorder(mVirtualDisplayContent,
mMediaProjectionManagerWrapper);
- spyOn(virtualDisplayContent);
+ spyOn(mVirtualDisplayContent);
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// record.
@@ -119,7 +125,7 @@
mDisplaySession.setDisplayToRecord(mDefaultDisplay.mDisplayId);
// GIVEN there is a window token associated with a task to record.
- sTaskWindowContainerToken = setUpTaskWindowContainerToken(virtualDisplayContent);
+ sTaskWindowContainerToken = setUpTaskWindowContainerToken(mVirtualDisplayContent);
mTaskSession = ContentRecordingSession.createTaskSession(sTaskWindowContainerToken);
mTaskSession.setVirtualDisplayId(displayId);
@@ -252,7 +258,11 @@
public void testOnConfigurationChanged_resizesSurface() {
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
- mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
+ // Ensure a different orientation when we check if something has changed.
+ @Configuration.Orientation final int lastOrientation =
+ mDisplayContent.getConfiguration().orientation == ORIENTATION_PORTRAIT
+ ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+ mContentRecorder.onConfigurationChanged(lastOrientation);
verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
anyFloat());
@@ -261,12 +271,53 @@
}
@Test
+ public void testOnConfigurationChanged_resizesVirtualDisplay() {
+ final int newWidth = 55;
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+
+ // The user rotates the device, so the host app resizes the virtual display for the capture.
+ resizeDisplay(mDisplayContent, newWidth, sSurfaceSize.y);
+ resizeDisplay(mVirtualDisplayContent, newWidth, sSurfaceSize.y);
+ mContentRecorder.onConfigurationChanged(mDisplayContent.getConfiguration().orientation);
+
+ verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
+ anyFloat());
+ verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
+ anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testOnConfigurationChanged_rotateVirtualDisplay() {
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+
+ // Change a value that we shouldn't rely upon; it has the wrong type.
+ mVirtualDisplayContent.setOverrideOrientation(SCREEN_ORIENTATION_FULL_SENSOR);
+ mContentRecorder.onConfigurationChanged(
+ mVirtualDisplayContent.getConfiguration().orientation);
+
+ // No resize is issued, only the initial transformations when we started recording.
+ verify(mTransaction).setPosition(eq(mRecordedSurface), anyFloat(),
+ anyFloat());
+ verify(mTransaction).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
+ anyFloat(), anyFloat());
+ }
+
+ @Test
public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
Configuration config = mTask.getConfiguration();
- config.orientation = ORIENTATION_PORTRAIT;
+ // Ensure a different orientation when we compare.
+ @Configuration.Orientation final int orientation =
+ config.orientation == ORIENTATION_PORTRAIT ? ORIENTATION_LANDSCAPE
+ : ORIENTATION_PORTRAIT;
+ final Rect lastBounds = config.windowConfiguration.getBounds();
+ config.orientation = orientation;
+ config.windowConfiguration.setBounds(
+ new Rect(0, 0, lastBounds.height(), lastBounds.width()));
mTask.onConfigurationChanged(config);
verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
@@ -279,13 +330,15 @@
public void testOnTaskBoundsConfigurationChanged_notifiesCallback() {
mTask.getRootTask().setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
+ final int minWidth = 222;
+ final int minHeight = 777;
final int recordedWidth = 333;
final int recordedHeight = 999;
final ActivityInfo info = new ActivityInfo();
info.windowLayout = new ActivityInfo.WindowLayout(-1 /* width */,
-1 /* widthFraction */, -1 /* height */, -1 /* heightFraction */,
- Gravity.NO_GRAVITY, recordedWidth, recordedHeight);
+ Gravity.NO_GRAVITY, minWidth, minHeight);
mTask.setMinDimensions(info);
// WHEN a recording is ongoing.
@@ -311,6 +364,39 @@
}
@Test
+ public void testTaskWindowingModeChanged_pip_stopsRecording() {
+ // WHEN a recording is ongoing.
+ mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ mContentRecorder.setContentRecordingSession(mTaskSession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ // WHEN a configuration change arrives, and the task is now pinned.
+ mTask.setWindowingMode(WINDOWING_MODE_PINNED);
+ Configuration configuration = mTask.getConfiguration();
+ mTask.onConfigurationChanged(configuration);
+
+ // THEN recording is paused.
+ assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+ }
+
+ @Test
+ public void testTaskWindowingModeChanged_fullscreen_startsRecording() {
+ // WHEN a recording is ongoing.
+ mTask.setWindowingMode(WINDOWING_MODE_PINNED);
+ mContentRecorder.setContentRecordingSession(mTaskSession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+
+ // WHEN the task is now fullscreen.
+ mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ mContentRecorder.updateRecording();
+
+ // THEN recording is started.
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+ }
+
+ @Test
public void testStartRecording_notifiesCallback_taskSession() {
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -335,6 +421,45 @@
}
@Test
+ public void testStartRecording_taskInPIP_recordingNotStarted() {
+ // GIVEN a task is in PIP.
+ mContentRecorder.setContentRecordingSession(mTaskSession);
+ mTask.setWindowingMode(WINDOWING_MODE_PINNED);
+
+ // WHEN a recording tries to start.
+ mContentRecorder.updateRecording();
+
+ // THEN recording does not start.
+ assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+ }
+
+ @Test
+ public void testStartRecording_taskInSplit_recordingStarted() {
+ // GIVEN a task is in PIP.
+ mContentRecorder.setContentRecordingSession(mTaskSession);
+ mTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+
+ // WHEN a recording tries to start.
+ mContentRecorder.updateRecording();
+
+ // THEN recording does not start.
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+ }
+
+ @Test
+ public void testStartRecording_taskInFullscreen_recordingStarted() {
+ // GIVEN a task is in PIP.
+ mContentRecorder.setContentRecordingSession(mTaskSession);
+ mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ // WHEN a recording tries to start.
+ mContentRecorder.updateRecording();
+
+ // THEN recording does not start.
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+ }
+
+ @Test
public void testOnVisibleRequestedChanged_notifiesCallback() {
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mTaskSession);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 994dcf1..ffa1ed9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -23,8 +23,6 @@
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
@@ -108,7 +106,7 @@
@Test
public void testControlsForDispatch_forceStatusBarVisible() {
- addStatusBar().mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
+ addStatusBar().mAttrs.forciblyShownTypes |= statusBars();
addNavigationBar();
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -120,8 +118,8 @@
@Test
public void testControlsForDispatch_statusBarForceShowNavigation() {
- addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.privateFlags |=
- PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+ addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.forciblyShownTypes |=
+ navigationBars();
addStatusBar();
addNavigationBar();
@@ -135,7 +133,7 @@
@Test
public void testControlsForDispatch_statusBarForceShowNavigation_butFocusedAnyways() {
WindowState notifShade = addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade");
- notifShade.mAttrs.privateFlags |= PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+ notifShade.mAttrs.forciblyShownTypes |= navigationBars();
addNavigationBar();
mDisplayContent.getInsetsPolicy().updateBarControlTarget(notifShade);
@@ -329,6 +327,7 @@
addNavigationBar().getControllableInsetProvider().getSource();
statusBarSource.setVisible(false);
navBarSource.setVisible(false);
+ mAppWindow.setRequestedVisibleTypes(0, navigationBars() | statusBars());
mAppWindow.mAboveInsetsState.addSource(navBarSource);
mAppWindow.mAboveInsetsState.addSource(statusBarSource);
final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
@@ -389,6 +388,50 @@
assertFalse(policy.isTransient(navigationBars()));
}
+ @Test
+ public void testFakeControlTarget_overrideVisibilityReceivedByWindows() {
+ final WindowState statusBar = addStatusBar();
+ final InsetsSourceProvider statusBarProvider = statusBar.getControllableInsetProvider();
+ statusBar.mSession.mCanForceShowingInsets = true;
+ statusBar.setHasSurface(true);
+ statusBarProvider.setServerVisible(true);
+
+ final InsetsSource statusBarSource = statusBarProvider.getSource();
+ final int statusBarId = statusBarSource.getId();
+ assertTrue(statusBarSource.isVisible());
+
+ final WindowState app1 = addWindow(TYPE_APPLICATION, "app1");
+ app1.mAboveInsetsState.addSource(statusBarSource);
+ assertTrue(app1.getInsetsState().peekSource(statusBarId).isVisible());
+
+ final WindowState app2 = addWindow(TYPE_APPLICATION, "app2");
+ app2.mAboveInsetsState.addSource(statusBarSource);
+ assertTrue(app2.getInsetsState().peekSource(statusBarId).isVisible());
+
+ app2.setRequestedVisibleTypes(0, navigationBars() | statusBars());
+ mDisplayContent.getInsetsPolicy().updateBarControlTarget(app2);
+ waitUntilWindowAnimatorIdle();
+
+ // app2 is the real control target now. It can override the visibility of all sources that
+ // it controls.
+ assertFalse(statusBarSource.isVisible());
+ assertFalse(app1.getInsetsState().peekSource(statusBarId).isVisible());
+ assertFalse(app2.getInsetsState().peekSource(statusBarId).isVisible());
+
+ statusBar.mAttrs.forciblyShownTypes = statusBars();
+ mDisplayContent.getDisplayPolicy().applyPostLayoutPolicyLw(
+ statusBar, statusBar.mAttrs, null, null);
+ mDisplayContent.getInsetsPolicy().updateBarControlTarget(app2);
+ waitUntilWindowAnimatorIdle();
+
+ // app2 is the fake control target now. It can only override the visibility of sources
+ // received by windows, but not the raw source.
+ assertTrue(statusBarSource.isVisible());
+ assertFalse(app1.getInsetsState().peekSource(statusBarId).isVisible());
+ assertFalse(app2.getInsetsState().peekSource(statusBarId).isVisible());
+
+ }
+
private WindowState addNavigationBar() {
final Binder owner = new Binder();
final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 114796d..2085d61 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -367,9 +367,11 @@
doReturn(rotatedState).when(app.mToken).getFixedRotationTransformInsetsState();
assertTrue(rotatedState.isSourceOrDefaultVisible(ID_STATUS_BAR, statusBars()));
- provider.getSource().setVisible(false);
+ app.setRequestedVisibleTypes(0, statusBars());
+ mDisplayContent.getInsetsPolicy().updateBarControlTarget(app);
mDisplayContent.getInsetsPolicy().showTransient(statusBars(),
true /* isGestureOnSystemBar */);
+ waitUntilWindowAnimatorIdle();
assertTrue(mDisplayContent.getInsetsPolicy().isTransient(statusBars()));
assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ID_STATUS_BAR, statusBars()));
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 94de462..8f68c0f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -2411,8 +2411,9 @@
.setUid(android.os.Process.myUid())
.build();
activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
- activity.mWmService.mLetterboxConfiguration
- .setUserAppAspectRatioSettingsOverrideEnabled(enabled);
+ spyOn(activity.mWmService.mLetterboxConfiguration);
+ doReturn(enabled).when(activity.mWmService.mLetterboxConfiguration)
+ .isUserAppAspectRatioSettingsEnabled();
// Set user aspect ratio override
final IPackageManager pm = mAtm.getPackageManager();
try {
@@ -4251,6 +4252,7 @@
// Set up a display in landscape with a fixed-orientation PORTRAIT app
setUpDisplaySizeWithApp(2800, 1400);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setIsAutomaticReachabilityInBookModeEnabled(false);
mWm.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f);
prepareUnresizable(mActivity, 1.75f, SCREEN_ORIENTATION_PORTRAIT);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index f1c5865..b028b47 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -22,6 +22,7 @@
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.os.Build;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -1567,6 +1568,13 @@
}
}
+ void deleteDataFor(String pkg) {
+ // reuse the existing prune method to delete data for the specified package.
+ // we'll use the current timestamp so that all events before now get pruned.
+ prunePackagesDataOnUpgrade(
+ new HashMap<>(Collections.singletonMap(pkg, SystemClock.elapsedRealtime())));
+ }
+
IntervalStats readIntervalStatsForFile(int interval, long fileName) {
synchronized (mLock) {
final IntervalStats stats = new IntervalStats();
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 90b798c..7db32a9 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2043,6 +2043,12 @@
mAppStandby.clearLastUsedTimestampsForTest(packageName, userId);
}
+ void deletePackageData(@NonNull String packageName, @UserIdInt int userId) {
+ synchronized (mLock) {
+ mUserState.get(userId).deleteDataFor(packageName);
+ }
+ }
+
private final class BinderService extends IUsageStatsManager.Stub {
private boolean hasPermission(String callingPackage) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsShellCommand.java b/services/usage/java/com/android/server/usage/UsageStatsShellCommand.java
index 772b22a..4cb31f9 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsShellCommand.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsShellCommand.java
@@ -38,6 +38,8 @@
switch (cmd) {
case "clear-last-used-timestamps":
return runClearLastUsedTimestamps();
+ case "delete-package-data":
+ return deletePackageData();
default:
return handleDefaultCommands(cmd);
}
@@ -51,14 +53,38 @@
pw.println(" Print this help text.");
pw.println();
pw.println("clear-last-used-timestamps PACKAGE_NAME [-u | --user USER_ID]");
- pw.println(" Clears any existing usage data for the given package.");
+ pw.println(" Clears the last used timestamps for the given package.");
+ pw.println();
+ pw.println("delete-package-data PACKAGE_NAME [-u | --user USER_ID]");
+ pw.println(" Deletes all the usage stats for the given package.");
pw.println();
}
@SuppressLint("AndroidFrameworkRequiresPermission")
private int runClearLastUsedTimestamps() {
final String packageName = getNextArgRequired();
+ final int userId = getUserId();
+ if (userId == -1) {
+ return -1;
+ }
+ mService.clearLastUsedTimestamps(packageName, userId);
+ return 0;
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private int deletePackageData() {
+ final String packageName = getNextArgRequired();
+ final int userId = getUserId();
+ if (userId == -1) {
+ return -1;
+ }
+
+ mService.deletePackageData(packageName, userId);
+ return 0;
+ }
+
+ private int getUserId() {
int userId = UserHandle.USER_CURRENT;
String opt;
while ((opt = getNextOption()) != null) {
@@ -72,8 +98,6 @@
if (userId == UserHandle.USER_CURRENT) {
userId = ActivityManager.getCurrentUser();
}
-
- mService.clearLastUsedTimestamps(packageName, userId);
- return 0;
+ return userId;
}
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index fd56b6e..7d2e1a4 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -974,6 +974,10 @@
mDatabase.dumpMappings(ipw);
}
+ void deleteDataFor(String pkg) {
+ mDatabase.deleteDataFor(pkg);
+ }
+
void dumpFile(IndentingPrintWriter ipw, String[] args) {
if (args == null || args.length == 0) {
// dump all files for every interval for specified user
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
index 3870dfd..4f99f14 100644
--- a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
@@ -95,21 +95,29 @@
String newTaskId = cinematicEffectRequest.getTaskId();
// Previous request is still being processed.
if (mCinematicEffectListenerWrapper != null) {
+ CinematicEffectResponse cinematicEffectResponse;
if (mCinematicEffectListenerWrapper.mTaskId.equals(newTaskId)) {
- invokeCinematicListenerAndCleanup(
- new CinematicEffectResponse.Builder(
- CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_PENDING, newTaskId)
- .build()
- );
+ cinematicEffectResponse = new CinematicEffectResponse.Builder(
+ CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_PENDING, newTaskId)
+ .build();
} else {
- invokeCinematicListenerAndCleanup(
- new CinematicEffectResponse.Builder(
- CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS,
- newTaskId).build()
- );
+ cinematicEffectResponse = new CinematicEffectResponse.Builder(
+ CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS,
+ newTaskId)
+ .build();
}
- return;
+ try {
+ cinematicEffectListener.onCinematicEffectGenerated(cinematicEffectResponse);
+ return;
+ } catch (RemoteException e) {
+ if (isDebug()) {
+ Slog.w(TAG, "RemoteException invoking cinematic effect listener for task["
+ + mCinematicEffectListenerWrapper.mTaskId + "]");
+ }
+ return;
+ }
}
+
RemoteWallpaperEffectsGenerationService remoteService = ensureRemoteServiceLocked();
if (remoteService != null) {
remoteService.executeOnResolvedService(
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
index 1a65611..e106f65 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
@@ -18,8 +18,6 @@
import android.app.Instrumentation
import android.tools.common.Rotation
-import android.tools.common.traces.Condition
-import android.tools.common.traces.DeviceStateDump
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.common.traces.component.IComponentMatcher
import android.tools.device.helpers.FIND_TIMEOUT
@@ -54,14 +52,14 @@
launchedAppComponentMatcherOverride: IComponentMatcher?,
action: String?,
stringExtras: Map<String, String>,
- waitConditions: Array<Condition<DeviceStateDump>>
+ waitConditionsBuilder: WindowManagerStateHelper.StateSyncBuilder
) {
super.launchViaIntent(
wmHelper,
launchedAppComponentMatcherOverride,
action,
stringExtras,
- waitConditions
+ waitConditionsBuilder
)
waitIMEShown(wmHelper)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 82d2ae0..c30786f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -244,12 +244,16 @@
action: String? = null,
stringExtras: Map<String, String>
) {
- launchViaIntentAndWaitShown(
+ launchViaIntent(
wmHelper,
launchedAppComponentMatcherOverride,
action,
stringExtras,
- waitConditions = arrayOf(ConditionsFactory.hasPipWindow())
+ waitConditionsBuilder = wmHelper
+ .StateSyncBuilder()
+ .add(ConditionsFactory.isWMStateComplete())
+ .withAppTransitionIdle()
+ .add(ConditionsFactory.hasPipWindow())
)
wmHelper
@@ -261,7 +265,7 @@
/** Expand the PIP window back to full screen via intent and wait until the app is visible */
fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) =
- launchViaIntentAndWaitShown(wmHelper)
+ launchViaIntent(wmHelper)
fun clickEnterPipButton(wmHelper: WindowManagerStateHelper) {
clickObject(ENTER_PIP_BUTTON_ID)
diff --git a/tests/TrustTests/Android.bp b/tests/TrustTests/Android.bp
index a1b888a..c216bce 100644
--- a/tests/TrustTests/Android.bp
+++ b/tests/TrustTests/Android.bp
@@ -25,6 +25,7 @@
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.test.uiautomator_uiautomator",
+ "flag-junit",
"mockito-target-minus-junit4",
"servicestests-utils",
"truth-prebuilt",
diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
index f864fed..1dfd5c0 100644
--- a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
+++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
@@ -16,6 +16,10 @@
package android.trust.test
+import android.content.pm.PackageManager
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.service.trust.GrantTrustResult
import android.trust.BaseTrustAgentService
import android.trust.TrustTestActivity
@@ -27,6 +31,7 @@
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.UiDevice
import com.android.server.testutils.mock
+import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -45,6 +50,7 @@
private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
private val lockStateTrackingRule = LockStateTrackingRule()
private val trustAgentRule = TrustAgentRule<GrantAndRevokeTrustAgent>()
+ private val packageManager = getInstrumentation().getTargetContext().getPackageManager()
@get:Rule
val rule: RuleChain = RuleChain
@@ -52,6 +58,7 @@
.around(ScreenLockRule())
.around(lockStateTrackingRule)
.around(trustAgentRule)
+ .around(DeviceFlagsValueProvider.createCheckFlagsRule())
@Before
fun manageTrust() {
@@ -72,7 +79,7 @@
trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) {}
uiDevice.sleep()
- lockStateTrackingRule.assertUnlocked()
+ lockStateTrackingRule.assertUnlockedAndTrusted()
}
@Test
@@ -86,6 +93,51 @@
}
@Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS)
+ fun grantCannotActivelyUnlockDevice() {
+ // On automotive, trust agents can actively unlock the device.
+ assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
+
+ // Lock the device.
+ uiDevice.sleep()
+ lockStateTrackingRule.assertLocked()
+
+ // Grant trust.
+ trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) {}
+
+ // The grant should not have unlocked the device. Wait a bit so that
+ // TrustManagerService probably will have finished processing the grant.
+ await()
+ lockStateTrackingRule.assertLocked()
+
+ // Turn the screen on and off to cause TrustManagerService to refresh
+ // its deviceLocked state. Then verify the state is still locked. This
+ // part failed before the fix for b/296464083.
+ uiDevice.wakeUp()
+ uiDevice.sleep()
+ await()
+ lockStateTrackingRule.assertLocked()
+ }
+
+ @Test
+ @RequiresFlagsDisabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS)
+ fun grantCouldCauseWrongDeviceLockedStateDueToBug() {
+ // On automotive, trust agents can actively unlock the device.
+ assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
+
+ // Verify that b/296464083 exists. That is, when the device is locked
+ // and a trust agent grants trust, the deviceLocked state incorrectly
+ // becomes false even though the device correctly remains locked.
+ uiDevice.sleep()
+ lockStateTrackingRule.assertLocked()
+ trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) {}
+ uiDevice.wakeUp()
+ uiDevice.sleep()
+ await()
+ lockStateTrackingRule.assertUnlockedButNotReally()
+ }
+
+ @Test
fun grantDoesNotCallBack() {
val callback = mock<(GrantTrustResult) -> Unit>()
trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0, callback)
diff --git a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
index ae72247..96362b8 100644
--- a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
+++ b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
@@ -102,7 +102,7 @@
trustAgentRule.agent.grantTrust(
GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {}
- lockStateTrackingRule.assertUnlocked()
+ lockStateTrackingRule.assertUnlockedAndTrusted()
}
@Test
@@ -125,7 +125,7 @@
Log.i(TAG, "Callback received; status=${it.status}")
result = it
}
- lockStateTrackingRule.assertUnlocked()
+ lockStateTrackingRule.assertUnlockedAndTrusted()
wait("callback triggered") { result?.status == STATUS_UNLOCKED_BY_GRANT }
}
diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
index c1a7bd9..5a8f828 100644
--- a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
@@ -16,6 +16,7 @@
package android.trust.test.lib
+import android.app.KeyguardManager
import android.app.trust.TrustManager
import android.content.Context
import android.util.Log
@@ -26,18 +27,23 @@
import org.junit.runners.model.Statement
/**
- * Rule for tracking the lock state of the device based on events emitted to [TrustListener].
+ * Rule for tracking the trusted state of the device based on events emitted to
+ * [TrustListener]. Provides helper methods for verifying that the trusted
+ * state has a particular value and is consistent with (a) the keyguard "locked"
+ * (i.e. showing) value when applicable, and (b) the device locked value that is
+ * tracked by TrustManagerService and is queryable via KeyguardManager.
*/
class LockStateTrackingRule : TestRule {
private val context: Context = getApplicationContext()
private val windowManager = checkNotNull(WindowManagerGlobal.getWindowManagerService())
+ private val keyguardManager = context.getSystemService(KeyguardManager::class.java) as KeyguardManager
- @Volatile lateinit var lockState: LockState
+ @Volatile lateinit var trustState: TrustState
private set
override fun apply(base: Statement, description: Description) = object : Statement() {
override fun evaluate() {
- lockState = LockState(locked = windowManager.isKeyguardLocked)
+ trustState = TrustState()
val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
val listener = Listener()
@@ -51,12 +57,25 @@
}
fun assertLocked() {
- wait("un-locked per TrustListener") { lockState.locked == true }
- wait("keyguard lock") { windowManager.isKeyguardLocked }
+ wait("device locked") { keyguardManager.isDeviceLocked }
+ // isDeviceLocked implies isKeyguardLocked && !trusted.
+ wait("keyguard locked") { windowManager.isKeyguardLocked }
+ wait("not trusted") { trustState.trusted == false }
}
- fun assertUnlocked() {
- wait("locked per TrustListener") { lockState.locked == false }
+ // TODO(b/299298338) remove this when removing FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS
+ fun assertUnlockedButNotReally() {
+ wait("device unlocked") { !keyguardManager.isDeviceLocked }
+ wait("not trusted") { trustState.trusted == false }
+ wait("keyguard locked") { windowManager.isKeyguardLocked }
+ }
+
+ fun assertUnlockedAndTrusted() {
+ wait("device unlocked") { !keyguardManager.isDeviceLocked }
+ wait("trusted") { trustState.trusted == true }
+ // Can't check for !isKeyguardLocked here, since isKeyguardLocked
+ // returns true in the case where the keyguard is dismissible with
+ // swipe, which is considered "device unlocked"!
}
inner class Listener : TestTrustListener() {
@@ -68,12 +87,12 @@
trustGrantedMessages: MutableList<String>
) {
Log.d(TAG, "Device became trusted=$enabled")
- lockState = lockState.copy(locked = !enabled)
+ trustState = trustState.copy(trusted=enabled)
}
}
- data class LockState(
- val locked: Boolean? = null
+ data class TrustState(
+ val trusted: Boolean? = null
)
companion object {
diff --git a/tools/aapt/ZipEntry.cpp b/tools/aapt/ZipEntry.cpp
index 5339285..6886993 100644
--- a/tools/aapt/ZipEntry.cpp
+++ b/tools/aapt/ZipEntry.cpp
@@ -18,6 +18,8 @@
// Access to entries in a Zip archive.
//
+#define _POSIX_THREAD_SAFE_FUNCTIONS // For mingw localtime_r().
+
#define LOG_TAG "zip"
#include "ZipEntry.h"
@@ -337,39 +339,26 @@
/*
* Set the CDE/LFH timestamp from UNIX time.
*/
-void ZipEntry::setModWhen(time_t when)
-{
-#if !defined(_WIN32)
- struct tm tmResult;
-#endif
- time_t even;
- unsigned short zdate, ztime;
-
- struct tm* ptm;
-
+void ZipEntry::setModWhen(time_t when) {
/* round up to an even number of seconds */
- even = (time_t)(((unsigned long)(when) + 1) & (~1));
+ time_t even = (time_t)(((unsigned long)(when) + 1) & (~1));
/* expand */
-#if !defined(_WIN32)
- ptm = localtime_r(&even, &tmResult);
-#else
- ptm = localtime(&even);
-#endif
+ struct tm tmResult;
+ struct tm* ptm = localtime_r(&even, &tmResult);
int year;
year = ptm->tm_year;
if (year < 80)
year = 80;
- zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
- ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
+ unsigned short zdate = (year - 80) << 9 | (ptm->tm_mon + 1) << 5 | ptm->tm_mday;
+ unsigned short ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
}
-
/*
* ===========================================================================
* ZipEntry::LocalFileHeader
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.h b/tools/aapt2/compile/InlineXmlFormatParser.h
index 4300023..3a5161b 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser.h
+++ b/tools/aapt2/compile/InlineXmlFormatParser.h
@@ -21,8 +21,8 @@
#include <vector>
#include "android-base/macros.h"
-
#include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
namespace aapt {