Merge "add core_libraries to the mapping list" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index efd8578..c231b30 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -57,7 +57,7 @@
":android.app.flags-aconfig-java{.generated_srcjars}",
":android.credentials.flags-aconfig-java{.generated_srcjars}",
":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
- ":com.android.server.flags.pinner-aconfig-java{.generated_srcjars}",
+ ":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
":android.service.controls.flags-aconfig-java{.generated_srcjars}",
":android.service.voice.flags-aconfig-java{.generated_srcjars}",
":android.media.tv.flags-aconfig-java{.generated_srcjars}",
@@ -588,16 +588,16 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
-// Pinner Service
+// Server Services Flags
aconfig_declarations {
- name: "com.android.server.flags.pinner-aconfig",
+ name: "com.android.server.flags.services-aconfig",
package: "com.android.server.flags",
- srcs: ["services/core/java/com/android/server/flags/pinner.aconfig"],
+ srcs: ["services/core/java/com/android/server/flags/*.aconfig"],
}
java_aconfig_library {
- name: "com.android.server.flags.pinner-aconfig-java",
- aconfig_declarations: "com.android.server.flags.pinner-aconfig",
+ name: "com.android.server.flags.services-aconfig-java",
+ aconfig_declarations: "com.android.server.flags.services-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
diff --git a/SECURITY_STATE_OWNERS b/SECURITY_STATE_OWNERS
new file mode 100644
index 0000000..30ddfe2
--- /dev/null
+++ b/SECURITY_STATE_OWNERS
@@ -0,0 +1,5 @@
+alxu@google.com
+musashi@google.com
+maunik@google.com
+davidkwak@google.com
+willcoster@google.com
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 900c902..d940e38 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1830,7 +1830,12 @@
/* system_measured_calling_download_bytes */0,
/* system_measured_calling_upload_bytes */ 0,
jobStatus.getJob().getIntervalMillis(),
- jobStatus.getJob().getFlexMillis());
+ jobStatus.getJob().getFlexMillis(),
+ jobStatus.hasFlexibilityConstraint(),
+ /* isFlexConstraintSatisfied */ false,
+ jobStatus.canApplyTransportAffinities(),
+ jobStatus.getNumAppliedFlexibleConstraints(),
+ jobStatus.getNumDroppedFlexibleConstraints());
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2273,7 +2278,12 @@
/* system_measured_calling_download_bytes */0,
/* system_measured_calling_upload_bytes */ 0,
cancelled.getJob().getIntervalMillis(),
- cancelled.getJob().getFlexMillis());
+ cancelled.getJob().getFlexMillis(),
+ cancelled.hasFlexibilityConstraint(),
+ cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+ cancelled.canApplyTransportAffinities(),
+ cancelled.getNumAppliedFlexibleConstraints(),
+ cancelled.getNumDroppedFlexibleConstraints());
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 3addf9f..fe55e27 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -536,7 +536,12 @@
/* system_measured_calling_download_bytes */ 0,
/* system_measured_calling_upload_bytes */ 0,
job.getJob().getIntervalMillis(),
- job.getJob().getFlexMillis());
+ job.getJob().getFlexMillis(),
+ job.hasFlexibilityConstraint(),
+ job.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+ job.canApplyTransportAffinities(),
+ job.getNumAppliedFlexibleConstraints(),
+ job.getNumDroppedFlexibleConstraints());
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1620,7 +1625,12 @@
TrafficStats.getUidTxBytes(completedJob.getUid())
- mInitialUploadedBytesFromCalling,
completedJob.getJob().getIntervalMillis(),
- completedJob.getJob().getFlexMillis());
+ completedJob.getJob().getFlexMillis(),
+ completedJob.hasFlexibilityConstraint(),
+ completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+ completedJob.canApplyTransportAffinities(),
+ completedJob.getNumAppliedFlexibleConstraints(),
+ completedJob.getNumDroppedFlexibleConstraints());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 0d5d11e..bdc2246 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -106,11 +106,8 @@
public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
public static final long NO_EARLIEST_RUNTIME = 0L;
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static final int CONSTRAINT_BATTERY_NOT_LOW =
JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -125,7 +122,7 @@
static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint
static final int CONSTRAINT_PREFETCH = 1 << 23;
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
- static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
+ public static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
private static final int IMPLICIT_CONSTRAINTS = 0
| CONSTRAINT_BACKGROUND_NOT_RESTRICTED
@@ -1534,7 +1531,7 @@
/**
* Returns the number of required flexible job constraints that have been dropped with time.
- * The lower this number is the easier it is for the flexibility constraint to be satisfied.
+ * The higher this number is the easier it is for the flexibility constraint to be satisfied.
*/
public int getNumDroppedFlexibleConstraints() {
return mNumDroppedFlexibleConstraints;
@@ -1600,7 +1597,8 @@
mTransportAffinitiesSatisfied = isSatisfied;
}
- boolean canApplyTransportAffinities() {
+ /** Whether transport affinities can be applied to the job in flex scheduling. */
+ public boolean canApplyTransportAffinities() {
return mCanApplyTransportAffinities;
}
diff --git a/core/api/current.txt b/core/api/current.txt
index b043503..43ff0c9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13422,12 +13422,12 @@
public final class SigningInfo implements android.os.Parcelable {
ctor public SigningInfo();
- ctor @FlaggedApi("android.content.pm.archiving") public SigningInfo(@IntRange(from=0) int, @Nullable java.util.Collection<android.content.pm.Signature>, @Nullable java.util.Collection<java.security.PublicKey>, @Nullable java.util.Collection<android.content.pm.Signature>);
+ ctor @FlaggedApi("android.content.pm.archiving") public SigningInfo(int, @Nullable java.util.Collection<android.content.pm.Signature>, @Nullable java.util.Collection<java.security.PublicKey>, @Nullable java.util.Collection<android.content.pm.Signature>);
ctor public SigningInfo(android.content.pm.SigningInfo);
method public int describeContents();
method public android.content.pm.Signature[] getApkContentsSigners();
method @FlaggedApi("android.content.pm.archiving") @NonNull public java.util.Collection<java.security.PublicKey> getPublicKeys();
- method @FlaggedApi("android.content.pm.archiving") @IntRange(from=0) public int getSchemeVersion();
+ method @FlaggedApi("android.content.pm.archiving") public int getSchemeVersion();
method public android.content.pm.Signature[] getSigningCertificateHistory();
method public boolean hasMultipleSigners();
method public boolean hasPastSigningCertificates();
@@ -57411,7 +57411,7 @@
method public abstract boolean getBuiltInZoomControls();
method public abstract int getCacheMode();
method public abstract String getCursiveFontFamily();
- method public abstract boolean getDatabaseEnabled();
+ method @Deprecated public abstract boolean getDatabaseEnabled();
method @Deprecated public abstract String getDatabasePath();
method public abstract int getDefaultFixedFontSize();
method public abstract int getDefaultFontSize();
@@ -57457,7 +57457,7 @@
method public abstract void setBuiltInZoomControls(boolean);
method public abstract void setCacheMode(int);
method public abstract void setCursiveFontFamily(String);
- method public abstract void setDatabaseEnabled(boolean);
+ method @Deprecated public abstract void setDatabaseEnabled(boolean);
method @Deprecated public abstract void setDatabasePath(String);
method public abstract void setDefaultFixedFontSize(int);
method public abstract void setDefaultFontSize(int);
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index c95b864..ec2e5fe 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -12,4 +12,11 @@
namespace: "app_widgets"
description: "Enable adapter conversion to RemoteCollectionItemsAdapter"
bug: "245950570"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "remove_app_widget_service_io_from_critical_path"
+ namespace: "app_widgets"
+ description: "Move state file IO to non-critical path"
+ bug: "312949280"
+}
diff --git a/core/java/android/content/pm/SigningDetails.java b/core/java/android/content/pm/SigningDetails.java
index 8c21974..bb09ad2 100644
--- a/core/java/android/content/pm/SigningDetails.java
+++ b/core/java/android/content/pm/SigningDetails.java
@@ -31,6 +31,8 @@
import libcore.util.HexEncoding;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.util.ArrayList;
@@ -49,6 +51,7 @@
private static final String TAG = "SigningDetails";
+ @Retention(RetentionPolicy.SOURCE)
@IntDef({SignatureSchemeVersion.UNKNOWN,
SignatureSchemeVersion.JAR,
SignatureSchemeVersion.SIGNING_BLOCK_V2,
diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java
index a407704..23daaf2 100644
--- a/core/java/android/content/pm/SigningInfo.java
+++ b/core/java/android/content/pm/SigningInfo.java
@@ -17,9 +17,9 @@
package android.content.pm;
import android.annotation.FlaggedApi;
-import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.SigningDetails.SignatureSchemeVersion;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
@@ -53,7 +53,7 @@
* schemas</a>
*/
@FlaggedApi(Flags.FLAG_ARCHIVING)
- public SigningInfo(@IntRange(from = 0) int schemeVersion,
+ public SigningInfo(@SignatureSchemeVersion int schemeVersion,
@Nullable Collection<Signature> apkContentsSigners,
@Nullable Collection<PublicKey> publicKeys,
@Nullable Collection<Signature> signingCertificateHistory) {
@@ -168,7 +168,7 @@
* schemas</a>
*/
@FlaggedApi(Flags.FLAG_ARCHIVING)
- public @IntRange(from = 0) int getSchemeVersion() {
+ public @SignatureSchemeVersion int getSchemeVersion() {
return mSigningDetails.getSignatureSchemeVersion();
}
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 209a595..d3f2c7a 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -92,3 +92,6 @@
per-file IThermal* = file:/THERMAL_OWNERS
per-file CoolingDevice.java = file:/THERMAL_OWNERS
per-file Temperature.java = file:/THERMAL_OWNERS
+
+# SecurityStateManager
+per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS
\ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4af657d..942ce971 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14339,6 +14339,14 @@
public static final String MODE_RINGER = "mode_ringer";
/**
+ * Whether or not Alarm stream should always be muted with Ringer.
+ *
+ * @hide
+ */
+ public static final String MUTE_ALARM_STREAM_WITH_RINGER_MODE =
+ "mute_alarm_stream_with_ringer_mode";
+
+ /**
* Overlay display devices setting.
* The associated value is a specially formatted string that describes the
* size and density of simulated secondary display devices.
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 14c5348..d12eda3 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1203,7 +1203,11 @@
* changes to this setting after that point.
*
* @param flag {@code true} if the WebView should use the database storage API
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract void setDatabaseEnabled(boolean flag);
/**
@@ -1236,7 +1240,11 @@
*
* @return {@code true} if the database storage API is enabled
* @see #setDatabaseEnabled
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract boolean getDatabaseEnabled();
/**
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 052e2f2..d3f3af7 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -610,6 +610,9 @@
// ringer mode.
optional SettingProto mode_ringer = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ // This is an optional feature where ringer mode affects alarm stream as well
+ optional SettingProto mute_alarm_stream_with_ringer_mode = 160 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
reserved 147; // Used to be apply_ramping_ringer
message MultiSim {
@@ -1086,5 +1089,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 160;
+ // Next tag = 161;
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5400c58..804e9ef 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2262,6 +2262,9 @@
<!-- The default min volume for the alarm stream -->
<integer name="config_audio_alarm_min_vol">1</integer>
+ <!-- Flag indicating if ringer mode affects alarm stream -->
+ <bool name="config_audio_ringer_mode_affects_alarm_stream">false</bool>
+
<!-- The default value for whether head tracking for
spatial audio is enabled for a newly connected audio device -->
<bool name="config_spatial_audio_head_tracking_enabled_default">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index eeef192..9589fb0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -288,6 +288,7 @@
<java-symbol type="integer" name="config_audio_ring_vol_default" />
<java-symbol type="integer" name="config_audio_ring_vol_steps" />
<java-symbol type="integer" name="config_audio_alarm_min_vol" />
+ <java-symbol type="bool" name="config_audio_ringer_mode_affects_alarm_stream" />
<java-symbol type="bool" name="config_spatial_audio_head_tracking_enabled_default" />
<java-symbol type="bool" name="config_avoidGfxAccel" />
<java-symbol type="bool" name="config_bluetooth_address_validation" />
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index da87339..afd554b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -360,9 +360,7 @@
if (activities == null) {
return null;
}
- // Already checked nullity in collectNonFinishingActivities.
- final Rect bounds = getInfo().getConfiguration().windowConfiguration.getBounds();
- return new ActivityStack(activities, isEmpty(), mToken, bounds, mOverlayTag);
+ return new ActivityStack(activities, isEmpty(), mToken, mOverlayTag);
}
/** Adds the activity that will be reparented to this container. */
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index d9fe733..3027c5f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -209,6 +209,7 @@
VALIDATORS.put(Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
VALIDATORS.put(Global.STYLUS_EVER_USED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.HAS_PAY_TOKENS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.GMS_CHECKIN_TIMEOUT_MIN, ANY_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 85e8769..6ad10cc 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -350,6 +350,7 @@
Settings.Global.DSRM_DURATION_MILLIS,
Settings.Global.DSRM_ENABLED_ACTIONS,
Settings.Global.MODE_RINGER,
+ Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE,
Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
Settings.Global.MULTI_SIM_SMS_PROMPT,
Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 2a9cf0f..55fc3a2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -20,10 +20,12 @@
import android.util.SizeF
import android.widget.FrameLayout
import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -31,11 +33,13 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Edit
@@ -98,7 +102,6 @@
modifier = modifier.fillMaxSize().background(Color.White),
) {
CommunalHubLazyGrid(
- modifier = Modifier.align(Alignment.CenterStart),
communalContent = communalContent,
viewModel = viewModel,
contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize),
@@ -138,21 +141,21 @@
@OptIn(ExperimentalFoundationApi::class)
@Composable
-private fun CommunalHubLazyGrid(
+private fun BoxScope.CommunalHubLazyGrid(
communalContent: List<CommunalContentModel>,
viewModel: BaseCommunalViewModel,
- modifier: Modifier = Modifier,
contentPadding: PaddingValues,
setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
updateDragPositionForRemove: (offset: Offset) -> Boolean,
) {
- var gridModifier = modifier
+ var gridModifier = Modifier.align(Alignment.CenterStart)
val gridState = rememberLazyGridState()
var list = communalContent
var dragDropState: GridDragDropState? = null
if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
val contentListState = rememberContentListState(communalContent, viewModel)
list = contentListState.list
+ // for drag & drop operations within the communal hub grid
dragDropState =
rememberGridDragDropState(
gridState = gridState,
@@ -164,9 +167,22 @@
.fillMaxSize()
.dragContainer(dragDropState, beforeContentPadding(contentPadding))
.onGloballyPositioned { setGridCoordinates(it) }
+ // for widgets dropped from other activities
+ val dragAndDropTargetState =
+ rememberDragAndDropTargetState(
+ gridState = gridState,
+ contentListState = contentListState,
+ updateDragPositionForRemove = updateDragPositionForRemove
+ )
+
+ // A full size box in background that listens to widget drops from the picker.
+ // Since the grid has its own listener for in-grid drag events, we use a separate element
+ // for android drag events.
+ Box(Modifier.fillMaxSize().dragAndDropTarget(dragAndDropTargetState)) {}
} else {
gridModifier = gridModifier.height(Dimensions.GridHeight)
}
+
LazyHorizontalGrid(
modifier = gridModifier,
state = gridState,
@@ -309,12 +325,24 @@
) {
when (model) {
is CommunalContentModel.Widget -> WidgetContent(model, size, elevation, modifier)
+ is CommunalContentModel.WidgetPlaceholder -> WidgetPlaceholderContent(size)
is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
is CommunalContentModel.Tutorial -> TutorialContent(modifier)
is CommunalContentModel.Umo -> Umo(viewModel, modifier)
}
}
+/** Presents a placeholder card for the new widget being dragged and dropping into the grid. */
+@Composable
+fun WidgetPlaceholderContent(size: SizeF) {
+ Card(
+ modifier = Modifier.size(Dp(size.width), Dp(size.height)),
+ colors = CardDefaults.cardColors(containerColor = Color.Transparent),
+ border = BorderStroke(3.dp, LocalAndroidColorScheme.current.tertiaryFixed),
+ shape = RoundedCornerShape(16.dp)
+ ) {}
+}
+
@Composable
private fun WidgetContent(
model: CommunalContentModel.Widget,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 89c5765..979991d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -16,11 +16,10 @@
package com.android.systemui.communal.ui.compose
+import android.content.ComponentName
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.toMutableStateList
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
@@ -32,6 +31,7 @@
return remember(communalContent) {
ContentListState(
communalContent,
+ viewModel::onAddWidget,
viewModel::onDeleteWidget,
viewModel::onReorderWidgets,
)
@@ -46,30 +46,57 @@
class ContentListState
internal constructor(
communalContent: List<CommunalContentModel>,
+ private val onAddWidget: (componentName: ComponentName, priority: Int) -> Unit,
private val onDeleteWidget: (id: Int) -> Unit,
- private val onReorderWidgets: (ids: List<Int>) -> Unit,
+ private val onReorderWidgets: (widgetIdToPriorityMap: Map<Int, Int>) -> Unit,
) {
- var list by mutableStateOf(communalContent)
+ var list = communalContent.toMutableStateList()
private set
/** Move item to a new position in the list. */
fun onMove(fromIndex: Int, toIndex: Int) {
- list = list.toMutableList().apply { add(toIndex, removeAt(fromIndex)) }
+ list.apply { add(toIndex, removeAt(fromIndex)) }
}
/** Remove widget from the list and the database. */
fun onRemove(indexToRemove: Int) {
if (list[indexToRemove] is CommunalContentModel.Widget) {
val widget = list[indexToRemove] as CommunalContentModel.Widget
- list = list.toMutableList().apply { removeAt(indexToRemove) }
+ list.apply { removeAt(indexToRemove) }
onDeleteWidget(widget.appWidgetId)
}
}
- /** Persist the new order with all the movements happened during dragging. */
- fun onSaveList() {
- val widgetIds: List<Int> =
- list.filterIsInstance<CommunalContentModel.Widget>().map { it.appWidgetId }
- onReorderWidgets(widgetIds)
+ /**
+ * Persists the new order with all the movements happened during drag operations & the new
+ * widget drop (if applicable).
+ *
+ * @param newItemComponentName name of the new widget that was dropped into the list; null if no
+ * new widget was added.
+ * @param newItemIndex index at which the a new widget was dropped into the list; null if no new
+ * widget was dropped.
+ */
+ fun onSaveList(newItemComponentName: ComponentName? = null, newItemIndex: Int? = null) {
+ // filters placeholder, but, maintains the indices of the widgets as if the placeholder was
+ // in the list. When persisted in DB, this leaves space for the new item (to be added) at
+ // the correct priority.
+ val widgetIdToPriorityMap: Map<Int, Int> =
+ list
+ .mapIndexedNotNull { index, item ->
+ if (item is CommunalContentModel.Widget) {
+ item.appWidgetId to list.size - index
+ } else {
+ null
+ }
+ }
+ .toMap()
+ // reorder and then add the new widget
+ onReorderWidgets(widgetIdToPriorityMap)
+ if (newItemComponentName != null && newItemIndex != null) {
+ onAddWidget(newItemComponentName, /*priority=*/ list.size - newItemIndex)
+ }
}
+
+ /** Returns true if the item at given index is editable. */
+ fun isItemEditable(index: Int) = list[index] is CommunalContentModel.Widget
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
new file mode 100644
index 0000000..22aa837
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -0,0 +1,274 @@
+/*
+ * 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.communal.ui.compose
+
+import android.content.ClipDescription
+import android.content.ComponentName
+import android.content.Intent
+import android.view.DragEvent
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.draganddrop.dragAndDropTarget
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draganddrop.DragAndDropEvent
+import androidx.compose.ui.draganddrop.DragAndDropTarget
+import androidx.compose.ui.draganddrop.mimeTypes
+import androidx.compose.ui.draganddrop.toAndroidDragEvent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.ui.compose.extensions.plus
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+
+/**
+ * Holds state associated with dragging and dropping items from other activities into the lazy grid.
+ *
+ * @see dragAndDropTarget
+ */
+@Composable
+internal fun rememberDragAndDropTargetState(
+ gridState: LazyGridState,
+ contentListState: ContentListState,
+ updateDragPositionForRemove: (offset: Offset) -> Boolean,
+): DragAndDropTargetState {
+ val scope = rememberCoroutineScope()
+ val autoScrollSpeed = remember { mutableFloatStateOf(0f) }
+ // Threshold of distance from edges that should start auto-scroll - chosen to be a narrow value
+ // that allows differentiating intention of scrolling from intention of dragging over the first
+ // visible item.
+ val autoScrollThreshold = with(LocalDensity.current) { 60.dp.toPx() }
+ val state =
+ remember(gridState, contentListState) {
+ DragAndDropTargetState(
+ state = gridState,
+ contentListState = contentListState,
+ scope = scope,
+ autoScrollSpeed = autoScrollSpeed,
+ autoScrollThreshold = autoScrollThreshold,
+ updateDragPositionForRemove = updateDragPositionForRemove,
+ )
+ }
+ LaunchedEffect(autoScrollSpeed.floatValue) {
+ if (autoScrollSpeed.floatValue != 0f) {
+ while (isActive) {
+ gridState.scrollBy(autoScrollSpeed.floatValue)
+ delay(10)
+ }
+ }
+ }
+ return state
+}
+
+/**
+ * Attaches a listener for drag and drop events from other activities.
+ *
+ * @see androidx.compose.foundation.draganddrop.dragAndDropTarget
+ * @see DragEvent
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+internal fun Modifier.dragAndDropTarget(
+ dragDropTargetState: DragAndDropTargetState,
+): Modifier {
+ val state by rememberUpdatedState(dragDropTargetState)
+
+ return this then
+ Modifier.dragAndDropTarget(
+ shouldStartDragAndDrop = accept@{ startEvent ->
+ startEvent.mimeTypes().any { it == ClipDescription.MIMETYPE_TEXT_INTENT }
+ },
+ target =
+ object : DragAndDropTarget {
+ override fun onStarted(event: DragAndDropEvent) {
+ state.onStarted()
+ }
+
+ override fun onMoved(event: DragAndDropEvent) {
+ state.onMoved(event)
+ }
+
+ override fun onDrop(event: DragAndDropEvent): Boolean {
+ return state.onDrop(event)
+ }
+
+ override fun onEnded(event: DragAndDropEvent) {
+ state.onEnded()
+ }
+ }
+ )
+}
+
+/**
+ * Handles dropping of an item coming from a different activity (e.g. widget picker) in to the grid
+ * corresponding to the provided [LazyGridState].
+ *
+ * Adds a placeholder container to highlight the anticipated location the widget will be dropped to.
+ * When the item is held over an empty area, the placeholder appears at the end of the grid if one
+ * didn't exist already. As user moves the item over an existing item, the placeholder appears in
+ * place of that existing item. And then, the existing item is pushed over as part of re-ordering.
+ *
+ * Once item is dropped, new ordering along with the dropped item is persisted. See
+ * [ContentListState.onSaveList].
+ *
+ * Difference between this and [GridDragDropState] is that, this is used for listening to drops from
+ * other activities. [GridDragDropState] on the other hand, handles dragging of existing items in
+ * the communal hub grid.
+ */
+internal class DragAndDropTargetState(
+ private val state: LazyGridState,
+ private val contentListState: ContentListState,
+ private val scope: CoroutineScope,
+ private val autoScrollSpeed: MutableState<Float>,
+ private val autoScrollThreshold: Float,
+ private val updateDragPositionForRemove: (offset: Offset) -> Boolean,
+) {
+ /**
+ * The placeholder item that is treated as if it is being dragged across the grid. It is added
+ * to grid once drag and drop event is started and removed when event ends.
+ */
+ private var placeHolder = CommunalContentModel.WidgetPlaceholder()
+
+ private var placeHolderIndex: Int? = null
+ private var isOnRemoveButton = false
+
+ fun onStarted() {
+ // assume item will be added to the end.
+ contentListState.list.add(placeHolder)
+ placeHolderIndex = contentListState.list.size - 1
+ }
+
+ fun onMoved(event: DragAndDropEvent) {
+ val dragEvent = event.toAndroidDragEvent()
+ isOnRemoveButton = updateDragPositionForRemove(Offset(dragEvent.x, dragEvent.y))
+ if (!isOnRemoveButton) {
+ findTargetItem(dragEvent)?.apply {
+ var scrollIndex: Int? = null
+ var scrollOffset: Int? = null
+ if (placeHolderIndex == state.firstVisibleItemIndex) {
+ // Save info about the first item before the move, to neutralize the automatic
+ // keeping first item first.
+ scrollIndex = placeHolderIndex
+ scrollOffset = state.firstVisibleItemScrollOffset
+ }
+
+ autoScrollIfNearEdges(dragEvent)
+
+ if (contentListState.isItemEditable(this.index)) {
+ movePlaceholderTo(this.index)
+ placeHolderIndex = this.index
+ }
+
+ if (scrollIndex != null && scrollOffset != null) {
+ // this is needed to neutralize automatic keeping the first item first.
+ scope.launch { state.scrollToItem(scrollIndex, scrollOffset) }
+ }
+ }
+ }
+ }
+
+ fun onDrop(event: DragAndDropEvent): Boolean {
+ autoScrollSpeed.value = 0f
+ if (isOnRemoveButton) {
+ return false
+ }
+ return placeHolderIndex?.let { dropIndex ->
+ val componentName = event.maybeWidgetComponentName()
+ if (componentName != null) {
+ // Placeholder isn't removed yet to allow the setting the right priority for items
+ // before adding in the new item.
+ contentListState.onSaveList(
+ newItemComponentName = componentName,
+ newItemIndex = dropIndex
+ )
+ return@let true
+ }
+ return false
+ }
+ ?: false
+ }
+
+ fun onEnded() {
+ autoScrollSpeed.value = 0f
+ placeHolderIndex = null
+ contentListState.list.remove(placeHolder)
+ isOnRemoveButton = updateDragPositionForRemove(Offset.Zero)
+ }
+
+ private fun autoScrollIfNearEdges(dragEvent: DragEvent) {
+ val orientation = state.layoutInfo.orientation
+ val distanceFromStart =
+ if (orientation == Orientation.Horizontal) {
+ dragEvent.x
+ } else {
+ dragEvent.y
+ }
+ val distanceFromEnd =
+ if (orientation == Orientation.Horizontal) {
+ state.layoutInfo.viewportSize.width - dragEvent.x
+ } else {
+ state.layoutInfo.viewportSize.height - dragEvent.y
+ }
+ autoScrollSpeed.value =
+ when {
+ distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd
+ distanceFromStart < autoScrollThreshold ->
+ -(autoScrollThreshold - distanceFromStart)
+ else -> 0f
+ }
+ }
+
+ private fun findTargetItem(dragEvent: DragEvent): LazyGridItemInfo? =
+ state.layoutInfo.visibleItemsInfo.firstOrNull { item ->
+ dragEvent.x.toInt() in item.offset.x..(item.offset + item.size).x &&
+ dragEvent.y.toInt() in item.offset.y..(item.offset + item.size).y
+ }
+
+ private fun movePlaceholderTo(index: Int) {
+ val currentIndex = contentListState.list.indexOf(placeHolder)
+ if (currentIndex != index) {
+ contentListState.onMove(currentIndex, index)
+ }
+ }
+
+ /**
+ * Parses and returns the component name of the widget that was dropped into the communal grid.
+ *
+ * Returns null if the drop event didn't include the widget information.
+ */
+ private fun DragAndDropEvent.maybeWidgetComponentName(): ComponentName? {
+ val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 }
+ return clipData
+ ?.getItemAt(0)
+ ?.intent
+ ?.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 5451d05..0d460aa8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -32,15 +32,13 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
import androidx.compose.ui.zIndex
-import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.ui.compose.extensions.plus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
@@ -112,7 +110,7 @@
.firstOrNull { item ->
// grid item offset is based off grid content container so we need to deduct
// before content padding from the initial pointer position
- item.isEditable &&
+ contentListState.isItemEditable(item.index) &&
(offset.x - contentOffset.x).toInt() in item.offset.x..item.offsetEnd.x &&
(offset.y - contentOffset.y).toInt() in item.offset.y..item.offsetEnd.y
}
@@ -149,7 +147,7 @@
val targetItem =
state.layoutInfo.visibleItemsInfo.find { item ->
- item.isEditable &&
+ contentListState.isItemEditable(item.index) &&
middleOffset.x.toInt() in item.offset.x..item.offsetEnd.x &&
middleOffset.y.toInt() in item.offset.y..item.offsetEnd.y &&
draggingItem.index != item.index
@@ -187,10 +185,6 @@
private val LazyGridItemInfo.offsetEnd: IntOffset
get() = this.offset + this.size
- /** Whether the grid item can be dragged or be a drop target. Only widget card is editable. */
- private val LazyGridItemInfo.isEditable: Boolean
- get() = contentListState.list[this.index] is CommunalContentModel.Widget
-
/** Calculate the amount dragged out of bound on both sides. Returns 0f if not overscrolled */
private fun checkForOverscroll(startOffset: Offset, endOffset: Offset): Float {
return when {
@@ -210,14 +204,6 @@
}
}
-private operator fun IntOffset.plus(size: IntSize): IntOffset {
- return IntOffset(x + size.width, y + size.height)
-}
-
-private operator fun Offset.plus(size: Size): Offset {
- return Offset(x + size.width, y + size.height)
-}
-
fun Modifier.dragContainer(
dragDropState: GridDragDropState,
beforeContentPadding: ContentPaddingInPx
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.kt
new file mode 100644
index 0000000..b86c07e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose.extensions
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+
+/** Adds the given size to the x and y offsets in this [IntOffset] */
+operator fun IntOffset.plus(size: IntSize): IntOffset {
+ return IntOffset(x + size.width, y + size.height)
+}
+
+/** Adds the given size to the x and y offsets in this [Offset]. */
+operator fun Offset.plus(size: Size): Offset {
+ return Offset(x + size.width, y + size.height)
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index a726b7c..b0beab9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -47,6 +47,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -54,7 +55,6 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
@@ -101,7 +101,6 @@
@Mock
private lateinit var unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController
@Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider
- @Mock private lateinit var secureSettings: SecureSettings
@Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback
@Mock private lateinit var udfpsController: UdfpsController
@Mock private lateinit var udfpsView: UdfpsView
@@ -117,6 +116,7 @@
@Mock
private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ @Mock private lateinit var shadeInteractor: ShadeInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -174,6 +174,7 @@
mSelectedUserInteractor,
{ deviceEntryUdfpsTouchOverlayViewModel },
{ defaultUdfpsTouchOverlayViewModel },
+ shadeInteractor
)
block()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index a4b55e7..e5da1f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -94,6 +94,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -205,6 +206,8 @@
@Mock
private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@Mock
+ private ShadeInteractor mShadeInteractor;
+ @Mock
private SinglePointerTouchProcessor mSinglePointerTouchProcessor;
@Mock
private SessionTracker mSessionTracker;
@@ -328,6 +331,7 @@
mActivityLaunchAnimator,
mBiometricExecutor,
mPrimaryBouncerInteractor,
+ mShadeInteractor,
mSinglePointerTouchProcessor,
mSessionTracker,
mAlternateBouncerInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index ac16c13..13b53a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -36,6 +36,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -70,6 +71,7 @@
protected @Mock UdfpsController mUdfpsController;
protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ protected @Mock ShadeInteractor mShadeInteractor;
protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
protected @Mock SelectedUserInteractor mSelectedUserInteractor;
@@ -149,7 +151,8 @@
mAlternateBouncerInteractor,
mUdfpsKeyguardAccessibilityDelegate,
mSelectedUserInteractor,
- mKeyguardTransitionInteractor);
+ mKeyguardTransitionInteractor,
+ mShadeInteractor);
return controller;
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 182712a..ddaa488 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -208,11 +208,11 @@
val repository = initCommunalWidgetRepository()
runCurrent()
- val ids = listOf(104, 103, 101)
- repository.updateWidgetOrder(ids)
+ val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
+ repository.updateWidgetOrder(widgetIdToPriorityMap)
runCurrent()
- verify(communalWidgetDao).updateWidgetOrder(ids)
+ verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
}
@Test
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index ab23564..83d415f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -403,6 +403,13 @@
final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
R.layout.biometric_prompt_layout, null, false);
+ /**
+ * View is only set visible in BiometricViewSizeBinder once PromptSize is determined
+ * that accounts for iconView size, to prevent prompt resizing being visible to the
+ * user.
+ * TODO(b/288175072): May be able to remove this once constraint layout is implemented
+ */
+ view.setVisibility(View.INVISIBLE);
mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
// TODO(b/201510778): This uses the wrong timeout in some cases
getJankListener(view, TRANSIT,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 66fb8ca..7d9ec08 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -23,16 +23,14 @@
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.statusbar.StatusBarState.SHADE
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
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
@@ -49,7 +47,7 @@
abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
view: T,
protected val statusBarStateController: StatusBarStateController,
- protected val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ protected val shadeInteractor: ShadeInteractor,
protected val dialogManager: SystemUIDialogManager,
private val dumpManager: DumpManager
) : ViewController<T>(view), Dumpable {
@@ -94,20 +92,18 @@
// 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)
+ listenForShadeExpansion(this)
}
}
}
@VisibleForTesting
- open suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+ suspend fun listenForShadeExpansion(scope: CoroutineScope): Job {
return scope.launch {
- primaryBouncerInteractor.bouncerExpansion.map { 1f - it }.collect { expansion: Float ->
- if (statusBarStateController.state != SHADE) {
- notificationShadeVisible = expansion > 0f
- view.onExpansionChanged(expansion)
- updatePauseAuth()
- }
+ shadeInteractor.anyExpansion.collect { expansion ->
+ notificationShadeVisible = expansion > 0f
+ view.onExpansionChanged(expansion)
+ updatePauseAuth()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index 03749a9..e7b0d9f 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.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
/**
@@ -26,13 +26,13 @@
class UdfpsBpViewController(
view: UdfpsBpView,
statusBarStateController: StatusBarStateController,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
+ shadeInteractor: ShadeInteractor,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsBpView>(
view,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
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 240728a..2fd13b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -89,6 +89,7 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -162,6 +163,7 @@
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
@NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
@NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ @NonNull private final ShadeInteractor mShadeInteractor;
@Nullable private final TouchProcessor mTouchProcessor;
@NonNull private final SessionTracker mSessionTracker;
@NonNull private final Lazy<DeviceEntryUdfpsTouchOverlayViewModel>
@@ -290,7 +292,8 @@
mKeyguardTransitionInteractor,
mSelectedUserInteractor,
mDeviceEntryUdfpsTouchOverlayViewModel,
- mDefaultUdfpsTouchOverlayViewModel
+ mDefaultUdfpsTouchOverlayViewModel,
+ mShadeInteractor
)));
}
@@ -656,6 +659,7 @@
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
@NonNull @BiometricsBackground Executor biometricsExecutor,
@NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
+ @NonNull ShadeInteractor shadeInteractor,
@NonNull SinglePointerTouchProcessor singlePointerTouchProcessor,
@NonNull SessionTracker sessionTracker,
@NonNull AlternateBouncerInteractor alternateBouncerInteractor,
@@ -705,6 +709,7 @@
mBiometricExecutor = biometricsExecutor;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
+ mShadeInteractor = shadeInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
mInputManager = inputManager;
mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index aabee93..b94a177 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -57,6 +57,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -107,6 +108,7 @@
private val selectedUserInteractor: SelectedUserInteractor,
private val deviceEntryUdfpsTouchOverlayViewModel: Lazy<DeviceEntryUdfpsTouchOverlayViewModel>,
private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>,
+ private val shadeInteractor: ShadeInteractor,
) {
private var overlayViewLegacy: UdfpsView? = null
private set
@@ -277,7 +279,7 @@
updateAccessibilityViewLocation(sensorBounds)
},
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
dialogManager,
dumpManager
)
@@ -303,6 +305,7 @@
udfpsKeyguardAccessibilityDelegate,
selectedUserInteractor,
transitionInteractor,
+ shadeInteractor,
)
}
REASON_AUTH_BP -> {
@@ -310,7 +313,7 @@
UdfpsBpViewController(
view.addUdfpsView(R.layout.udfps_bp_view),
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
dialogManager,
dumpManager
)
@@ -320,7 +323,7 @@
UdfpsFpmEmptyViewController(
view.addUdfpsView(R.layout.udfps_fpm_empty_view),
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
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 88002e7..ab3fbb1 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.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
/**
@@ -28,13 +28,13 @@
class UdfpsFpmEmptyViewController(
view: UdfpsFpmEmptyView,
statusBarStateController: StatusBarStateController,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
+ shadeInteractor: ShadeInteractor,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsFpmEmptyView>(
view,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
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 64148f6..9f17024 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -36,6 +36,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -68,16 +69,17 @@
systemUIDialogManager: SystemUIDialogManager,
private val udfpsController: UdfpsController,
private val activityLaunchAnimator: ActivityLaunchAnimator,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
private val selectedUserInteractor: SelectedUserInteractor,
private val transitionInteractor: KeyguardTransitionInteractor,
+ shadeInteractor: ShadeInteractor,
) :
UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
view,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
systemUIDialogManager,
dumpManager,
) {
@@ -319,7 +321,7 @@
}
@VisibleForTesting
- override suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+ suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
return scope.launch {
primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
inputBouncerExpansion = bouncerExpansion
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 90e4a38..a7fb6f7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -97,7 +97,13 @@
val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
-
+ /**
+ * View is only set visible in BiometricViewSizeBinder once PromptSize is determined that
+ * accounts for iconView size, to prevent prompt resizing being visible to the user.
+ *
+ * TODO(b/288175072): May be able to remove this once constraint layout is implemented
+ */
+ iconView.addLottieOnCompositionLoadedListener { viewModel.setIsIconViewLoaded(true) }
PromptIconViewBinder.bind(
iconView,
iconOverlayView,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 7e16d1e..f340bd8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -30,7 +30,6 @@
import androidx.core.view.doOnLayout
import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
-import com.android.systemui.res.R
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.ui.BiometricPromptLayout
@@ -41,6 +40,8 @@
import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
import com.android.systemui.biometrics.ui.viewmodel.isSmall
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
/** Helper for [BiometricViewBinder] to handle resize transitions. */
@@ -92,8 +93,22 @@
// TODO(b/251476085): migrate the legacy panel controller and simplify this
view.repeatWhenAttached {
var currentSize: PromptSize? = null
+
lifecycleScope.launch {
- viewModel.size.collect { size ->
+ /**
+ * View is only set visible in BiometricViewSizeBinder once PromptSize is
+ * determined that accounts for iconView size, to prevent prompt resizing being
+ * visible to the user.
+ *
+ * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
+ * layout is implemented
+ */
+ combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
+ (isIconViewLoaded, size) ->
+ if (!isIconViewLoaded) {
+ return@collect
+ }
+
// prepare for animated size transitions
for (v in viewsToHideWhenSmall) {
v.showTextOrHide(forceHide = size.isSmall)
@@ -196,8 +211,9 @@
}
}
}
-
currentSize = size
+ view.visibility = View.VISIBLE
+ viewModel.setIsIconViewLoaded(false)
notifyAccessibilityChanged()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 6d0a58e..d899827e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -192,6 +192,28 @@
val iconViewModel: PromptIconViewModel =
PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
+ private val _isIconViewLoaded = MutableStateFlow(false)
+
+ /**
+ * For prompts with an iconView, false until the prompt's iconView animation has been loaded in
+ * the view, otherwise true by default. Used for BiometricViewSizeBinder to wait for the icon
+ * asset to be loaded before determining the prompt size.
+ */
+ val isIconViewLoaded: Flow<Boolean> =
+ combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded
+ ->
+ if (credentialKind is PromptKind.Biometric) {
+ isIconViewLoaded
+ } else {
+ true
+ }
+ }
+
+ // Sets whether the prompt's iconView animation has been loaded in the view yet.
+ fun setIsIconViewLoaded(iconViewLoaded: Boolean) {
+ _isIconViewLoaded.value = iconViewLoaded
+ }
+
/** Padding for prompt UI elements */
val promptPadding: Flow<Rect> =
combine(size, displayStateInteractor.currentRotation) { size, rotation ->
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index a12db6f..779446d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -117,10 +117,10 @@
fun updateItemRank(itemUid: Long, order: Int)
@Transaction
- fun updateWidgetOrder(ids: List<Int>) {
- ids.forEachIndexed { index, it ->
- val widget = getWidgetByIdNow(it)
- updateItemRank(widget.itemId, ids.size - index)
+ fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
+ widgetIdToPriorityMap.forEach { (id, priority) ->
+ val widget = getWidgetByIdNow(id)
+ updateItemRank(widget.itemId, priority)
}
}
@@ -129,7 +129,7 @@
return insertWidget(
widgetId = widgetId,
componentName = provider.flattenToString(),
- insertItemRank(priority),
+ itemId = insertItemRank(priority),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index ded5581..d1bbe57 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -62,8 +62,12 @@
/** Delete a widget by id from app widget service and the database. */
fun deleteWidget(widgetId: Int) {}
- /** Update the order of widgets in the database. */
- fun updateWidgetOrder(ids: List<Int>) {}
+ /**
+ * Update the order of widgets in the database.
+ *
+ * @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
+ */
+ fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -168,11 +172,11 @@
}
}
- override fun updateWidgetOrder(ids: List<Int>) {
+ override fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
applicationScope.launch(bgDispatcher) {
- communalWidgetDao.updateWidgetOrder(ids)
+ communalWidgetDao.updateWidgetOrder(widgetIdToPriorityMap)
logger.i({ "Updated the order of widget list with ids: $str1." }) {
- str1 = ids.toString()
+ str1 = widgetIdToPriorityMap.toString()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index e342c6b..0f4e583 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -102,8 +102,13 @@
/** Delete a widget by id. */
fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
- /** Reorder widgets. The order in the list will be their display order in the hub. */
- fun updateWidgetOrder(ids: List<Int>) = widgetRepository.updateWidgetOrder(ids)
+ /**
+ * Reorder the widgets.
+ *
+ * @param widgetIdToPriorityMap mapping of the widget ids to their new priorities.
+ */
+ fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
+ widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
/** A list of widget content to be displayed in the communal hub. */
val widgetContent: Flow<List<CommunalContentModel.Widget>> =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index bb9b4b5..3ae5229 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -20,6 +20,7 @@
import android.appwidget.AppWidgetProviderInfo
import android.widget.RemoteViews
import com.android.systemui.communal.shared.model.CommunalContentSize
+import java.util.UUID
/** Encapsulates data for a communal content. */
sealed interface CommunalContentModel {
@@ -39,6 +40,13 @@
override val size = CommunalContentSize.HALF
}
+ /** A placeholder item representing a new widget being added */
+ class WidgetPlaceholder : CommunalContentModel {
+ override val key: String = "widget_placeholder_${UUID.randomUUID()}"
+ // Same as widget size.
+ override val size = CommunalContentSize.HALF
+ }
+
class Tutorial(
id: Int,
override val size: CommunalContentSize,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 708f137..577e404 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.ui.viewmodel
+import android.content.ComponentName
import android.os.PowerManager
import android.os.SystemClock
import android.view.MotionEvent
@@ -53,6 +54,13 @@
communalInteractor.setTransitionState(transitionState)
}
+ /**
+ * Called when a widget is added via drag and drop from the widget picker into the communal hub.
+ */
+ fun onAddWidget(componentName: ComponentName, priority: Int) {
+ communalInteractor.addWidget(componentName, priority)
+ }
+
// TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
// touches anymore.
/** Called when a touch is received outside the edge swipe area when hub mode is closed. */
@@ -82,8 +90,14 @@
/** Called as the UI requests deleting a widget. */
open fun onDeleteWidget(id: Int) {}
- /** Called as the UI requests reordering widgets. */
- open fun onReorderWidgets(ids: List<Int>) {}
+ /**
+ * Called as the UI requests reordering widgets.
+ *
+ * @param widgetIdToPriorityMap mapping of the widget ids to its priority. When re-ordering to
+ * add a new item in the middle, provide the priorities of existing widgets as if the new item
+ * existed, and then, call [onAddWidget] to add the new item at intended order.
+ */
+ open fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) {}
/** Called as the UI requests opening the widget editor. */
open fun onOpenWidgetEditor() {}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index c82e000..368df9e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -47,5 +47,6 @@
override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
- override fun onReorderWidgets(ids: List<Int>) = communalInteractor.updateWidgetOrder(ids)
+ override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
+ communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 0a13e48..c936c63 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -40,6 +40,7 @@
private var windowManagerService: IWindowManager? = null,
) : ComponentActivity() {
companion object {
+ private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
private const val EXTRA_FILTER_STRATEGY = "filter_strategy"
private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1
private const val TAG = "EditWidgetsActivity"
@@ -49,10 +50,23 @@
registerForActivityResult(StartActivityForResult()) { result ->
when (result.resultCode) {
RESULT_OK -> {
- result.data
- ?.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
- ?.let { communalInteractor.addWidget(it, 0) }
- ?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
+ result.data?.let { intent ->
+ val isPendingWidgetDrag =
+ intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false)
+ // Nothing to do when a widget is being dragged & dropped. The drop
+ // target in the communal grid will receive the widget to be added (if
+ // the user drops it over).
+ if (!isPendingWidgetDrag) {
+ intent
+ .getParcelableExtra(
+ Intent.EXTRA_COMPONENT_NAME,
+ ComponentName::class.java
+ )
+ ?.let { communalInteractor.addWidget(it, 0) }
+ ?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
+ }
+ }
+ ?: run { Log.w(TAG, "No data in result.") }
}
else ->
Log.w(
@@ -65,8 +79,6 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setShowWhenLocked(true)
-
setCommunalEditWidgetActivityContent(
activity = this,
viewModel = communalViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 64ff3b0c..1277585 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -32,11 +32,8 @@
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
/**
* Assists in creating sub-flows for a KeyguardTransition. Call [setup] once for a transition, and
@@ -68,8 +65,6 @@
* in the range of [0, 1]. View animations should begin and end within a subset of this
* range. This function maps the [startTime] and [duration] into [0, 1], when this subset is
* valid.
- *
- * Will produce a [SharedFlow], so that identical animations can use the same value.
*/
fun sharedFlow(
duration: Duration,
@@ -80,7 +75,7 @@
onFinish: (() -> Float)? = null,
interpolator: Interpolator = LINEAR,
name: String? = null
- ): SharedFlow<Float> {
+ ): Flow<Float> {
if (!duration.isPositive()) {
throw IllegalArgumentException("duration must be a positive number: $duration")
}
@@ -137,7 +132,6 @@
value
}
.filterNotNull()
- .shareIn(scope, SharingStarted.WhileSubscribed())
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index 425da5f..48bf7ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -27,6 +27,7 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -48,7 +49,7 @@
override val subId: Int,
startingIsCarrierMerged: Boolean,
override val tableLogBuffer: TableLogBuffer,
- subscriptionModel: StateFlow<SubscriptionModel?>,
+ subscriptionModel: Flow<SubscriptionModel?>,
private val defaultNetworkName: NetworkNameModel,
private val networkNameSeparator: String,
@Application scope: CoroutineScope,
@@ -331,7 +332,7 @@
fun build(
subId: Int,
startingIsCarrierMerged: Boolean,
- subscriptionModel: StateFlow<SubscriptionModel?>,
+ subscriptionModel: Flow<SubscriptionModel?>,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
): FullMobileConnectionRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 4fb99c24..be2c21b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -96,7 +96,7 @@
class MobileConnectionRepositoryImpl(
override val subId: Int,
private val context: Context,
- subscriptionModel: StateFlow<SubscriptionModel?>,
+ subscriptionModel: Flow<SubscriptionModel?>,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
connectivityManager: ConnectivityManager,
@@ -448,7 +448,7 @@
fun build(
subId: Int,
mobileLogger: TableLogBuffer,
- subscriptionModel: StateFlow<SubscriptionModel?>,
+ subscriptionModel: Flow<SubscriptionModel?>,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
): MobileConnectionRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 2a510e4..a455db2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -357,10 +357,10 @@
@VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
- private fun subscriptionModelForSubId(subId: Int): StateFlow<SubscriptionModel?> {
- return subscriptions
- .map { list -> list.firstOrNull { model -> model.subscriptionId == subId } }
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+ private fun subscriptionModelForSubId(subId: Int): Flow<SubscriptionModel?> {
+ return subscriptions.map { list ->
+ list.firstOrNull { model -> model.subscriptionId == subId }
+ }
}
private fun createRepositoryForSubId(subId: Int): FullMobileConnectionRepository {
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 e2aa984..647dae6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -20,10 +20,9 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
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
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import org.junit.Assert.assertFalse
import org.junit.Before
@@ -42,8 +41,7 @@
@Mock lateinit var udfpsBpView: UdfpsBpView
@Mock lateinit var statusBarStateController: StatusBarStateController
- @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
- @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+ @Mock lateinit var shadeInteractor: ShadeInteractor
@Mock lateinit var systemUIDialogManager: SystemUIDialogManager
@Mock lateinit var dumpManager: DumpManager
@@ -55,7 +53,7 @@
UdfpsBpViewController(
udfpsBpView,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
systemUIDialogManager,
dumpManager
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index 16b2ed6..9c5cd71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -140,21 +140,76 @@
}
assertThat(widgets())
.containsExactly(
+ communalItemRankEntry2,
+ communalWidgetItemEntry2,
communalItemRankEntry1,
communalWidgetItemEntry1,
- communalItemRankEntry2,
+ )
+ .inOrder()
+
+ // swapped priorities
+ val widgetIdsToPriorityMap = mapOf(widgetInfo1.widgetId to 2, widgetInfo2.widgetId to 1)
+ communalWidgetDao.updateWidgetOrder(widgetIdsToPriorityMap)
+ assertThat(widgets())
+ .containsExactly(
+ communalItemRankEntry1.copy(rank = 2),
+ communalWidgetItemEntry1,
+ communalItemRankEntry2.copy(rank = 1),
communalWidgetItemEntry2
)
+ .inOrder()
+ }
- val widgetIdsInNewOrder = listOf(widgetInfo2.widgetId, widgetInfo1.widgetId)
- communalWidgetDao.updateWidgetOrder(widgetIdsInNewOrder)
+ @Test
+ fun addNewWidgetWithReorder_emitsWidgetsInNewOrder() =
+ testScope.runTest {
+ val existingWidgets = listOf(widgetInfo1, widgetInfo2)
+ val widgets = collectLastValue(communalWidgetDao.getWidgets())
+
+ existingWidgets.forEach {
+ val (widgetId, provider, priority) = it
+ communalWidgetDao.addWidget(
+ widgetId = widgetId,
+ provider = provider,
+ priority = priority,
+ )
+ }
assertThat(widgets())
.containsExactly(
communalItemRankEntry2,
communalWidgetItemEntry2,
communalItemRankEntry1,
- communalWidgetItemEntry1
+ communalWidgetItemEntry1,
)
+ .inOrder()
+
+ // map with no item in the middle at index 1
+ val widgetIdsToIndexMap = mapOf(widgetInfo1.widgetId to 1, widgetInfo2.widgetId to 3)
+ communalWidgetDao.updateWidgetOrder(widgetIdsToIndexMap)
+ assertThat(widgets())
+ .containsExactly(
+ communalItemRankEntry2.copy(rank = 3),
+ communalWidgetItemEntry2,
+ communalItemRankEntry1.copy(rank = 1),
+ communalWidgetItemEntry1,
+ )
+ .inOrder()
+ // add the new middle item that we left space for.
+ communalWidgetDao.addWidget(
+ widgetId = widgetInfo3.widgetId,
+ provider = widgetInfo3.provider,
+ priority = 2,
+ )
+ assertThat(widgets())
+ .containsExactly(
+ communalItemRankEntry2.copy(rank = 3),
+ communalWidgetItemEntry2,
+ communalItemRankEntry3.copy(rank = 2),
+ communalWidgetItemEntry3,
+ communalItemRankEntry1.copy(rank = 1),
+ communalWidgetItemEntry1,
+ )
+ .inOrder()
}
data class FakeWidgetMetadata(
@@ -176,8 +231,15 @@
provider = ComponentName("pk_name", "cls_name_2"),
priority = 2
)
+ val widgetInfo3 =
+ FakeWidgetMetadata(
+ widgetId = 3,
+ provider = ComponentName("pk_name", "cls_name_3"),
+ priority = 3
+ )
val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.priority)
val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.priority)
+ val communalItemRankEntry3 = CommunalItemRank(uid = 3L, rank = widgetInfo3.priority)
val communalWidgetItemEntry1 =
CommunalWidgetItem(
uid = 1L,
@@ -192,5 +254,12 @@
componentName = widgetInfo2.provider.flattenToString(),
itemId = communalItemRankEntry2.uid,
)
+ val communalWidgetItemEntry3 =
+ CommunalWidgetItem(
+ uid = 3L,
+ widgetId = widgetInfo3.widgetId,
+ componentName = widgetInfo3.provider.flattenToString(),
+ itemId = communalItemRankEntry3.uid,
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 2bee7b8..d3febf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository;
import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -151,6 +152,7 @@
@Test
public void testOnConnectReadStatusBarSetting() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
NotificationListener.NotificationSettingsListener settingsListener =
mock(NotificationListener.NotificationSettingsListener.class);
mListener.addNotificationSettingsListener(settingsListener);
@@ -164,6 +166,7 @@
@Test
public void testOnStatusBarIconsBehaviorChanged() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
NotificationListener.NotificationSettingsListener settingsListener =
mock(NotificationListener.NotificationSettingsListener.class);
mListener.addNotificationSettingsListener(settingsListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 2df6e46..d4300f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -17,6 +17,7 @@
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
@@ -71,6 +72,7 @@
@Test
fun testShadeWidth_BasedOnFractionToShade() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
setFractionToShade(0f)
setOnLockscreen(true)
@@ -86,6 +88,7 @@
@Test
fun testShelfIsLong_WhenNotOnLockscreen() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
setFractionToShade(0f)
setOnLockscreen(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 62d8f7f..9f4e1dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -73,6 +74,7 @@
@Test
fun calculateWidthFor_fiveIcons_widthForFourIcons() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
iconContainer.setIconSize(10)
@@ -151,7 +153,7 @@
iconContainer.addView(iconFive)
assertEquals(5, iconContainer.childCount)
- val width = iconContainer.calculateWidthFor(/* numIcons= */ 5f)
+ val width = iconContainer.calculateWidthFor(/* numIcons= */ 4f)
iconContainer.setActualLayoutWidth(width.toInt())
iconContainer.calculateIconXTranslations()
@@ -212,6 +214,7 @@
@Test
fun shouldForceOverflow_appearingAboveSpeedBump_true() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
val forceOverflow =
iconContainer.shouldForceOverflow(
/* i= */ 1,
@@ -228,7 +231,7 @@
iconContainer.shouldForceOverflow(
/* i= */ 10,
/* speedBumpIndex= */ 11,
- /* iconAppearAmount= */ 0f,
+ /* iconAppearAmount= */ 0.1f,
/* maxVisibleIcons= */ 5
)
assertTrue(forceOverflow)
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index a4b2896..cab2d74 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.server.appwidget;
+import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -144,6 +145,7 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -277,7 +279,12 @@
mKeyguardManager = (KeyguardManager) mContext.getSystemService(KEYGUARD_SERVICE);
mDevicePolicyManagerInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- mSaveStateHandler = BackgroundThread.getHandler();
+ if (removeAppWidgetServiceIoFromCriticalPath()) {
+ mSaveStateHandler = new Handler(BackgroundThread.get().getLooper(),
+ this::handleSaveMessage);
+ } else {
+ mSaveStateHandler = BackgroundThread.getHandler();
+ }
final ServiceThread serviceThread = new ServiceThread(TAG,
android.os.Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
serviceThread.start();
@@ -314,6 +321,40 @@
mMaxWidgetBitmapMemory = 6 * size.x * size.y;
}
+ private boolean handleSaveMessage(Message msg) {
+ final int userId = msg.what;
+ SparseArray<byte[]> userIdToBytesMapping;
+ synchronized (mLock) {
+ // No need to enforce unlocked state when there is no caller. User can be in the
+ // stopping state or removed by the time the message is processed
+ ensureGroupStateLoadedLocked(userId, false /* enforceUserUnlockingOrUnlocked */);
+ userIdToBytesMapping = saveStateToByteArrayLocked(userId);
+ }
+
+ for (int i = 0; i < userIdToBytesMapping.size(); i++) {
+ int currentProfileId = userIdToBytesMapping.keyAt(i);
+ byte[] currentStateByteArray = userIdToBytesMapping.valueAt(i);
+ AtomicFile currentFile = getSavedStateFile(currentProfileId);
+ FileOutputStream fileStream;
+ try {
+ fileStream = currentFile.startWrite();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to start writing stream", e);
+ continue;
+ }
+
+ try {
+ fileStream.write(currentStateByteArray);
+ currentFile.finishWrite(fileStream);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write state byte stream to file", e);
+ currentFile.failWrite(fileStream);
+ }
+ }
+
+ return true;
+ }
+
private void registerBroadcastReceiver() {
// Register for broadcasts about package install, etc., so we can
// update the provider list.
@@ -1944,7 +1985,12 @@
}
private void saveGroupStateAsync(int groupId) {
- mSaveStateHandler.post(new SaveStateRunnable(groupId));
+ if (removeAppWidgetServiceIoFromCriticalPath()) {
+ mSaveStateHandler.removeMessages(groupId);
+ mSaveStateHandler.sendEmptyMessage(groupId);
+ } else {
+ mSaveStateHandler.post(new SaveStateRunnable(groupId));
+ }
}
private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
@@ -3104,6 +3150,23 @@
}
@GuardedBy("mLock")
+ private @NonNull SparseArray<byte[]> saveStateToByteArrayLocked(int userId) {
+ tagProvidersAndHosts();
+
+ final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
+ SparseArray<byte[]> userIdToBytesMapping = new SparseArray<>();
+
+ for (int profileId : profileIds) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ if (writeProfileStateToStreamLocked(outputStream, profileId)) {
+ userIdToBytesMapping.put(profileId, outputStream.toByteArray());
+ }
+ }
+
+ return userIdToBytesMapping;
+ }
+
+ @GuardedBy("mLock")
private void saveStateLocked(int userId) {
tagProvidersAndHosts();
@@ -3117,7 +3180,7 @@
FileOutputStream stream;
try {
stream = file.startWrite();
- if (writeProfileStateToFileLocked(stream, profileId)) {
+ if (writeProfileStateToStreamLocked(stream, profileId)) {
file.finishWrite(stream);
} else {
file.failWrite(stream);
@@ -3158,7 +3221,7 @@
}
@GuardedBy("mLock")
- private boolean writeProfileStateToFileLocked(FileOutputStream stream, int userId) {
+ private boolean writeProfileStateToStreamLocked(OutputStream stream, int userId) {
int N;
try {
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index c258370..e289a56 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -27,6 +27,7 @@
per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS
per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
+per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS
per-file *SoundTrigger* = file:/media/java/android/media/soundtrigger/OWNERS
per-file *Storage* = file:/core/java/android/os/storage/OWNERS
per-file *TimeUpdate* = file:/services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 3ae55271..d461643 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -8303,7 +8303,7 @@
r.mFgsNotificationShown,
durationMs,
r.mStartForegroundCount,
- ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
+ 0, // Short instance name -- no longer logging it.
r.mFgsHasNotificationPermission,
r.foregroundServiceType,
fgsTypeCheckCode,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a80d2fd..f8451fd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -164,6 +164,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.MemoryStatUtil.hasMemcg;
import static com.android.server.am.ProcessList.ProcStartHandler;
+import static com.android.server.flags.Flags.disableSystemCompaction;
import static com.android.server.net.NetworkPolicyManagerInternal.updateBlockedReasonsWithProcState;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
@@ -8564,8 +8565,10 @@
final long now = SystemClock.uptimeMillis();
final long timeSinceLastIdle = now - mLastIdleTime;
- // Compact all non-zygote processes to freshen up the page cache.
- mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
+ if (!disableSystemCompaction()) {
+ // Compact all non-zygote processes to freshen up the page cache.
+ mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
+ }
final long lowRamSinceLastIdle = mAppProfiler.getLowRamTimeSinceIdleLPr(now);
mLastIdleTime = now;
diff --git a/services/core/java/com/android/server/am/ActivityManagerUtils.java b/services/core/java/com/android/server/am/ActivityManagerUtils.java
index 01466b8..78a2ecb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerUtils.java
+++ b/services/core/java/com/android/server/am/ActivityManagerUtils.java
@@ -129,14 +129,6 @@
}
/**
- * @param shortInstanceName {@link ServiceRecord#shortInstanceName}.
- * @return hash of the ServiceRecord's shortInstanceName, combined with ANDROID_ID.
- */
- public static int hashComponentNameForAtom(String shortInstanceName) {
- return getUnsignedHashUnCached(shortInstanceName) ^ getAndroidIdHash();
- }
-
- /**
* Helper method to log an unsafe intent event.
*/
public static void logUnsafeIntentEvent(int event, int callingUid,
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 05303f6f..b68572f 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -519,7 +519,7 @@
r.mFgsNotificationShown,
0, // durationMs
r.mStartForegroundCount,
- ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
+ 0, // Short instance name -- no longer logging it.
r.mFgsHasNotificationPermission,
r.foregroundServiceType,
0,
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ea791b7..37fe389 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -44,6 +44,7 @@
import static com.android.media.audio.Flags.alarmMinVolumeZero;
import static com.android.media.audio.Flags.bluetoothMacAddressAnonymization;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
+import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
import static com.android.server.utils.EventLogger.Event.ALOGE;
import static com.android.server.utils.EventLogger.Event.ALOGI;
@@ -607,6 +608,7 @@
};
private final boolean mUseFixedVolume;
+ private final boolean mRingerModeAffectsAlarm;
private final boolean mUseVolumeGroupAliases;
// If absolute volume is supported in AVRCP device
@@ -1300,6 +1302,9 @@
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
+ mRingerModeAffectsAlarm = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_audio_ringer_mode_affects_alarm_stream);
+
mRecordMonitor = new RecordingActivityMonitor(mContext);
mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
@@ -7019,6 +7024,19 @@
ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
}
+ if (ringerModeAffectsAlarm()) {
+ if (mRingerModeAffectsAlarm) {
+ boolean muteAlarmWithRinger =
+ mSettings.getGlobalInt(mContentResolver,
+ Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE,
+ /* def= */ 0) != 0;
+ if (muteAlarmWithRinger) {
+ ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_ALARM);
+ } else {
+ ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_ALARM);
+ }
+ }
+ }
if (ringerModeAffectedStreams != mRingerModeAffectedStreams) {
mSettings.putSystemIntForUser(mContentResolver,
Settings.System.MODE_RINGER_STREAMS_AFFECTED,
@@ -9678,6 +9696,8 @@
Settings.Global.ZEN_MODE), false, this);
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.ZEN_MODE_CONFIG_ETAG), false, this);
+ mContentResolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE), false, this);
mContentResolver.registerContentObserver(Settings.System.getUriFor(
Settings.System.MODE_RINGER_STREAMS_AFFECTED), false, this);
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
diff --git a/services/core/java/com/android/server/flags/OWNERS b/services/core/java/com/android/server/flags/OWNERS
index 535a750..60ceb12 100644
--- a/services/core/java/com/android/server/flags/OWNERS
+++ b/services/core/java/com/android/server/flags/OWNERS
@@ -1 +1,2 @@
-per-file pinner.aconfig = edgararriaga@google.com
\ No newline at end of file
+per-file pinner.aconfig = edgararriaga@google.com
+per-file compaction.aconfig = edgararriaga@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/flags/compaction.aconfig b/services/core/java/com/android/server/flags/compaction.aconfig
new file mode 100644
index 0000000..58cc560
--- /dev/null
+++ b/services/core/java/com/android/server/flags/compaction.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.flags"
+
+flag {
+ name: "disable_system_compaction"
+ namespace: "system_performance"
+ description: "This flag controls if all processes compaction should happen during idle maintenance."
+ bug: "314328789"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index a6c5ad5..a88d85e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -229,31 +229,6 @@
}
}
- private static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList(
- String enabledInputMethodsStr,
- TextUtils.SimpleStringSplitter inputMethodSplitter,
- TextUtils.SimpleStringSplitter subtypeSplitter) {
- ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
- if (TextUtils.isEmpty(enabledInputMethodsStr)) {
- return imsList;
- }
- inputMethodSplitter.setString(enabledInputMethodsStr);
- while (inputMethodSplitter.hasNext()) {
- String nextImsStr = inputMethodSplitter.next();
- subtypeSplitter.setString(nextImsStr);
- if (subtypeSplitter.hasNext()) {
- ArrayList<String> subtypeHashes = new ArrayList<>();
- // The first element is ime id.
- String imeId = subtypeSplitter.next();
- while (subtypeSplitter.hasNext()) {
- subtypeHashes.add(subtypeSplitter.next());
- }
- imsList.add(new Pair<>(imeId, subtypeHashes));
- }
- }
- return imsList;
- }
-
InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId,
boolean copyOnWrite) {
mMethodMap = methodMap;
@@ -351,9 +326,30 @@
}
List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
- return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR),
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR));
+ final String enabledInputMethodsStr = getEnabledInputMethodsStr();
+ final TextUtils.SimpleStringSplitter inputMethodSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+ final TextUtils.SimpleStringSplitter subtypeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+ final ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
+ if (TextUtils.isEmpty(enabledInputMethodsStr)) {
+ return imsList;
+ }
+ inputMethodSplitter.setString(enabledInputMethodsStr);
+ while (inputMethodSplitter.hasNext()) {
+ String nextImsStr = inputMethodSplitter.next();
+ subtypeSplitter.setString(nextImsStr);
+ if (subtypeSplitter.hasNext()) {
+ ArrayList<String> subtypeHashes = new ArrayList<>();
+ // The first element is ime id.
+ String imeId = subtypeSplitter.next();
+ while (subtypeSplitter.hasNext()) {
+ subtypeHashes.add(subtypeSplitter.next());
+ }
+ imsList.add(new Pair<>(imeId, subtypeHashes));
+ }
+ }
+ return imsList;
}
void appendAndPutEnabledInputMethodLocked(String id) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f49d51c..13bf336c 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -10754,6 +10754,14 @@
final String key = record.getSbn().getKey();
final NotificationListenerService.Ranking ranking =
new NotificationListenerService.Ranking();
+ ArrayList<Notification.Action> smartActions = record.getSystemGeneratedSmartActions();
+ ArrayList<CharSequence> smartReplies = record.getSmartReplies();
+ if (redactSensitiveNotificationsFromUntrustedListeners()
+ && !mListeners.isUidTrusted(info.uid)
+ && mListeners.hasSensitiveContent(record)) {
+ smartActions = null;
+ smartReplies = null;
+ }
ranking.populate(
key,
rankings.size(),
@@ -10771,8 +10779,8 @@
record.isHidden(),
record.getLastAudiblyAlertedMs(),
record.getSound() != null || record.getVibration() != null,
- record.getSystemGeneratedSmartActions(),
- record.getSmartReplies(),
+ smartActions,
+ smartReplies,
record.canBubble(),
record.isTextChanged(),
record.isConversation(),
@@ -11522,20 +11530,16 @@
super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet);
String pkgName = getPackageName(pkgOrComponent);
if (redactSensitiveNotificationsFromUntrustedListeners()) {
- try {
- int uid = mPackageManagerClient.getPackageUidAsUser(pkgName, userId);
- if (!enabled) {
- synchronized (mTrustedListenerUids) {
- mTrustedListenerUids.remove(uid);
- }
+ int uid = mPackageManagerInternal.getPackageUid(pkgName, 0, userId);
+ if (!enabled && uid >= 0) {
+ synchronized (mTrustedListenerUids) {
+ mTrustedListenerUids.remove(uid);
}
- if (enabled && isAppTrustedNotificationListenerService(uid, pkgName)) {
- synchronized (mTrustedListenerUids) {
- mTrustedListenerUids.add(uid);
- }
+ }
+ if (enabled && uid >= 0 && isAppTrustedNotificationListenerService(uid, pkgName)) {
+ synchronized (mTrustedListenerUids) {
+ mTrustedListenerUids.add(uid);
}
- } catch (NameNotFoundException e) {
- Slog.e(TAG, "PackageManager could not find package " + pkgName, e);
}
}
@@ -11955,8 +11959,10 @@
for (final ManagedServiceInfo info : getServices()) {
boolean isTrusted = isUidTrusted(info.uid);
- boolean sendRedacted = isNewSensitive && !isTrusted;
- boolean sendOldRedacted = isOldSensitive && !isTrusted;
+ boolean sendRedacted = redactSensitiveNotificationsFromUntrustedListeners()
+ && isNewSensitive && !isTrusted;
+ boolean sendOldRedacted = redactSensitiveNotificationsFromUntrustedListeners()
+ && isOldSensitive && !isTrusted;
boolean sbnVisible = isVisibleToListener(sbn, r.getNotificationType(), info);
boolean oldSbnVisible = (oldSbn != null)
&& isVisibleToListener(oldSbn, old.getNotificationType(), info);
@@ -12055,7 +12061,7 @@
StatusBarNotification redactStatusBarNotification(StatusBarNotification sbn) {
if (!redactSensitiveNotificationsFromUntrustedListeners()) {
- return sbn;
+ throw new RuntimeException("redactStatusBarNotification called while flag is off");
}
ApplicationInfo appInfo = sbn.getNotification().extras.getParcelable(
@@ -12227,6 +12233,7 @@
public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) {
boolean isHiddenRankingUpdate = changedHiddenNotifications != null
&& changedHiddenNotifications.size() > 0;
+
// TODO (b/73052211): if the ranking update changed the notification type,
// cancel notifications for NLSes that can't see them anymore
for (final ManagedServiceInfo serviceInfo : getServices()) {
@@ -12250,7 +12257,6 @@
if (notifyThisListener || !isHiddenRankingUpdate) {
final NotificationRankingUpdate update = makeRankingUpdateLocked(
serviceInfo);
-
mHandler.post(() -> notifyRankingUpdate(serviceInfo, update));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS
index 2d36ff3..d49bc43 100644
--- a/services/tests/servicestests/src/com/android/server/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/OWNERS
@@ -7,3 +7,4 @@
per-file BatteryServiceTest.java = file:platform/hardware/interfaces:/health/OWNERS
per-file GestureLauncherServiceTest.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
per-file PinnerServiceTest.java = file:/apct-tests/perftests/OWNERS
+per-file SecurityStateTest.java = file:/SECURITY_STATE_OWNERS
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index 2868b7e..ae36839 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -68,10 +68,7 @@
import android.os.Parcel;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.INotificationListener;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.NotificationListenerService;
@@ -111,7 +108,7 @@
public class NotificationListenersTest extends UiServiceTestCase {
@Rule
- public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private PackageManager mPm;
@@ -696,8 +693,8 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_withPermission() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
when(mNm.mPackageManager.checkUidPermission(RECEIVE_SENSITIVE_NOTIFICATIONS, mUid1))
.thenReturn(PERMISSION_GRANTED);
ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
@@ -706,8 +703,8 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_withSystemSignature() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
when(mNm.mPackageManagerInternal.isPlatformSigned(mCn1.getPackageName())).thenReturn(true);
ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
mListeners.onServiceAdded(info);
@@ -715,8 +712,8 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_withCdmAssociation() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
mNm.mCompanionManager = mock(ICompanionDeviceManager.class);
AssociationInfo assocInfo = mock(AssociationInfo.class);
when(assocInfo.isRevoked()).thenReturn(false);
@@ -731,16 +728,16 @@
}
@Test
- @RequiresFlagsDisabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_ifFlagDisabled() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
mListeners.onServiceAdded(info);
assertTrue(mListeners.isUidTrusted(mUid1));
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_whenPosted() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
infos.add(getMockServiceInfo());
doReturn(infos).when(mListeners).getServices();
@@ -762,13 +759,11 @@
mListeners.notifyPostedLocked(r, old);
verify(mListeners, atLeast(1)).redactStatusBarNotification(eq(sbn));
verify(mListeners, never()).redactStatusBarNotification(eq(oldSbn));
-
-
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_whenPosted_oldRemoved() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
infos.add(getMockServiceInfo());
doReturn(infos).when(mListeners).getServices();
@@ -795,8 +790,8 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_whenRemoved() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
doReturn(mock(StatusBarNotification.class))
.when(mListeners).redactStatusBarNotification(any());
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
@@ -816,8 +811,8 @@
}
@Test
- @RequiresFlagsDisabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_noneIfFlagDisabled() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
infos.add(getMockServiceInfo());
doReturn(infos).when(mListeners).getServices();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 884ea31..9408a8b 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -77,7 +77,9 @@
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.service.notification.Adjustment.KEY_CONTEXTUAL_ACTIONS;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.KEY_TEXT_REPLIES;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
import static android.service.notification.Condition.SOURCE_CONTEXT;
@@ -216,9 +218,6 @@
import android.os.WorkSource;
import android.permission.PermissionManager;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.rule.DeniedDevices;
import android.platform.test.rule.DeviceProduct;
@@ -361,9 +360,6 @@
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
- @Rule
- public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
private TestableNotificationManagerService mService;
private INotificationManager mBinderService;
private NotificationManagerInternal mInternalService;
@@ -11778,8 +11774,8 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testGetActiveNotificationsFromListener_redactNotification() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
NotificationRecord r =
generateNotificationRecord(mTestNotificationChannel, 0, 0);
mService.addNotification(r);
@@ -11808,12 +11804,11 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testGetSnoozedNotificationsFromListener_redactNotification() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
NotificationRecord r =
generateNotificationRecord(mTestNotificationChannel, 0, 0);
- mService.addNotification(r);
- mService.snoozeNotificationInt(r.getKey(), 1000, null, mListener);
+ when(mSnoozeHelper.getSnoozed()).thenReturn(List.of(r));
when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
when(mListeners.hasSensitiveContent(any())).thenReturn(true);
StatusBarNotification redacted = generateRedactedSbn(mTestNotificationChannel, 1, 1);
@@ -11993,6 +11988,97 @@
}
@Test
+ public void testMakeRankingUpdate_redactsIfRecordSensitiveAndServiceUntrusted() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+ when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgA);
+ mService.addNotification(pkgA);
+ NotificationRecord pkgB = new NotificationRecord(mContext,
+ generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgB);
+ mService.addNotification(pkgB);
+
+ ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+ when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(info.isSameUser(anyInt())).thenReturn(true);
+ NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+ NotificationListenerService.Ranking ranking =
+ nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(0, ranking.getSmartActions().size());
+ assertEquals(0, ranking.getSmartReplies().size());
+ NotificationListenerService.Ranking ranking2 =
+ nru.getRankingMap().getRawRankingObject(pkgB.getSbn().getKey());
+ assertEquals(0, ranking2.getSmartActions().size());
+ assertEquals(0, ranking2.getSmartReplies().size());
+ }
+
+ @Test
+ public void testMakeRankingUpdate_doestntRedactIfFlagDisabled() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+ when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgA);
+
+ mService.addNotification(pkgA);
+ ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+ when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(info.isSameUser(anyInt())).thenReturn(true);
+ NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+ NotificationListenerService.Ranking ranking =
+ nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(1, ranking.getSmartActions().size());
+ assertEquals(1, ranking.getSmartReplies().size());
+ }
+
+ @Test
+ public void testMakeRankingUpdate_doesntRedactIfNotSensitiveOrServiceTrusted() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgA);
+
+ mService.addNotification(pkgA);
+ ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+ when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(info.isSameUser(anyInt())).thenReturn(true);
+
+ // No sensitive content, no redaction
+ when(mListeners.isUidTrusted(eq(1000))).thenReturn(false);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(false);
+ NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+ NotificationListenerService.Ranking ranking =
+ nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(1, ranking.getSmartActions().size());
+ assertEquals(1, ranking.getSmartReplies().size());
+
+ // trusted listener, no redaction
+ when(mListeners.isUidTrusted(eq(1000))).thenReturn(true);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+ nru = mService.makeRankingUpdateLocked(info);
+ ranking = nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(1, ranking.getSmartActions().size());
+ assertEquals(1, ranking.getSmartReplies().size());
+ }
+
+ private void addSmartActionsAndReplies(NotificationRecord record) {
+ Bundle b = new Bundle();
+ ArrayList<Notification.Action> actions = new ArrayList<>();
+ actions.add(new Notification.Action(0, "", null));
+ b.putParcelableArrayList(KEY_CONTEXTUAL_ACTIONS, actions);
+ ArrayList<CharSequence> replies = new ArrayList<>(List.of("test"));
+ b.putCharSequenceArrayList(KEY_TEXT_REPLIES, replies);
+ Adjustment a = new Adjustment(record.getSbn().getPackageName(), record.getSbn().getKey(),
+ b, "", record.getUserId());
+ record.addAdjustment(a);
+ record.applyAdjustments();
+ }
+
+ @Test
public void testMaybeShowReviewPermissionsNotification_flagOff() {
mService.setShowReviewPermissionsNotification(false);
reset(mMockNm);