Merge "Fix notification shade pull down not pausing UDFPS authentication" 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/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/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/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/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/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/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/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/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