Merge "Launch intents from notifications from main thread"
diff --git a/api/current.txt b/api/current.txt
index 8cb5b0d..532c3d9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -45976,7 +45976,9 @@
method public final void addExistingConnection(android.telecom.PhoneAccountHandle, android.telecom.Connection);
method public final void conferenceRemoteConnections(android.telecom.RemoteConnection, android.telecom.RemoteConnection);
method public final void connectionServiceFocusReleased();
+ method @Nullable public final android.telecom.RemoteConference createRemoteIncomingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
method public final android.telecom.RemoteConnection createRemoteIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+ method @Nullable public final android.telecom.RemoteConference createRemoteOutgoingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
method public final android.telecom.RemoteConnection createRemoteOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
method public final java.util.Collection<android.telecom.Conference> getAllConferences();
method public final java.util.Collection<android.telecom.Connection> getAllConnections();
@@ -46207,6 +46209,7 @@
public final class RemoteConnection {
method public void abort();
+ method public void addConferenceParticipants(@NonNull java.util.List<android.net.Uri>);
method public void answer();
method public void disconnect();
method public android.net.Uri getAddress();
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index 0ba7f60..f9d0d14 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -44126,7 +44126,9 @@
method public final void addExistingConnection(android.telecom.PhoneAccountHandle, android.telecom.Connection);
method public final void conferenceRemoteConnections(android.telecom.RemoteConnection, android.telecom.RemoteConnection);
method public final void connectionServiceFocusReleased();
+ method @Nullable public final android.telecom.RemoteConference createRemoteIncomingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
method public final android.telecom.RemoteConnection createRemoteIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+ method @Nullable public final android.telecom.RemoteConference createRemoteOutgoingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
method public final android.telecom.RemoteConnection createRemoteOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
method public final java.util.Collection<android.telecom.Conference> getAllConferences();
method public final java.util.Collection<android.telecom.Connection> getAllConnections();
@@ -44357,6 +44359,7 @@
public final class RemoteConnection {
method public void abort();
+ method public void addConferenceParticipants(@NonNull java.util.List<android.net.Uri>);
method public void answer();
method public void disconnect();
method public android.net.Uri getAddress();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index ee2bb6b1..3e11067 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -307,7 +307,7 @@
private void onInflationFinished(NotificationEntry entry) {
mLogger.logNotifInflated(entry.getKey());
mInflatingNotifs.remove(entry);
- mViewBarn.registerViewForEntry(entry, entry.getRow());
+ mViewBarn.registerViewForEntry(entry, entry.getRowController());
mInflationStates.put(entry, STATE_INFLATED);
mNotifInflatingFilter.invalidateList();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 8849824..f90ec0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -136,6 +136,7 @@
.expandableNotificationRow(row)
.notificationEntry(entry)
.onExpandClickListener(mPresenter)
+ .listContainer(mListContainer)
.build();
ExpandableNotificationRowController rowController =
component.getExpandableNotificationRowController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index 9782c3e..1c02c62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -29,8 +29,7 @@
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.render.NotifViewManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifViewManagerBuilder;
+import com.android.systemui.statusbar.notification.collection.render.ShadeViewManagerFactory;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import java.io.FileDescriptor;
@@ -51,7 +50,7 @@
private final NotifCoordinators mNotifPluggableCoordinators;
private final NotifInflaterImpl mNotifInflater;
private final DumpManager mDumpManager;
- private final NotifViewManagerBuilder mNotifViewManagerBuilder;
+ private final ShadeViewManagerFactory mShadeViewManagerFactory;
private final FeatureFlags mFeatureFlags;
@@ -64,7 +63,7 @@
NotifCoordinators notifCoordinators,
NotifInflaterImpl notifInflater,
DumpManager dumpManager,
- NotifViewManagerBuilder notifViewManagerBuilder,
+ ShadeViewManagerFactory shadeViewManagerFactory,
FeatureFlags featureFlags) {
mPipelineWrapper = pipelineWrapper;
mGroupCoalescer = groupCoalescer;
@@ -73,8 +72,8 @@
mNotifPluggableCoordinators = notifCoordinators;
mDumpManager = dumpManager;
mNotifInflater = notifInflater;
+ mShadeViewManagerFactory = shadeViewManagerFactory;
mFeatureFlags = featureFlags;
- mNotifViewManagerBuilder = notifViewManagerBuilder;
}
/** Hooks the new pipeline up to NotificationManager */
@@ -95,8 +94,7 @@
// Wire up pipeline
if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
- NotifViewManager notifViewManager = mNotifViewManagerBuilder.build(listContainer);
- notifViewManager.attach(mListBuilder);
+ mShadeViewManagerFactory.create(listContainer).attach(mListBuilder);
}
mListBuilder.attach(mNotifCollection);
mNotifCollection.attach(mGroupCoalescer);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
new file mode 100644
index 0000000..67f7b1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import android.view.View
+import java.lang.RuntimeException
+import java.lang.StringBuilder
+
+/**
+ * A controller that represents a single unit of addable/removable view(s) in the notification
+ * shade. Some nodes are just a single view (such as a header), while some might involve many views
+ * (such as a notification row).
+ *
+ * It's possible for nodes to support having child nodes (for example, some notification rows
+ * contain other notification rows). If so, they must implement all of the child-related methods
+ * below.
+ */
+interface NodeController {
+ /** A string that uniquely(ish) represents the node in the tree. Used for debugging. */
+ val nodeLabel: String
+
+ val view: View
+
+ fun getChildAt(index: Int): View? {
+ throw RuntimeException("Not supported")
+ }
+
+ fun getChildCount(): Int {
+ throw RuntimeException("Not supported")
+ }
+
+ fun addChildAt(child: NodeController, index: Int) {
+ throw RuntimeException("Not supported")
+ }
+
+ fun moveChildTo(child: NodeController, index: Int) {
+ throw RuntimeException("Not supported")
+ }
+
+ fun removeChild(child: NodeController, isTransfer: Boolean) {
+ throw RuntimeException("Not supported")
+ }
+}
+
+/**
+ * Used to specify the tree of [NodeController]s that currently make up the shade.
+ */
+interface NodeSpec {
+ val parent: NodeSpec?
+ val controller: NodeController
+ val children: List<NodeSpec>
+}
+
+class NodeSpecImpl(
+ override val parent: NodeSpec?,
+ override val controller: NodeController
+) : NodeSpec {
+ override val children = mutableListOf<NodeSpec>()
+}
+
+/**
+ * Converts a tree spec to human-readable string, for dumping purposes.
+ */
+fun treeSpecToStr(tree: NodeSpec): String {
+ return StringBuilder().also { treeSpecToStrHelper(tree, it, "") }.toString()
+}
+
+private fun treeSpecToStrHelper(tree: NodeSpec, sb: StringBuilder, indent: String) {
+ sb.append("${indent}ns{${tree.controller.nodeLabel}")
+ if (tree.children.isNotEmpty()) {
+ val childIndent = "$indent "
+ for (child in tree.children) {
+ treeSpecToStrHelper(child, sb, childIndent)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
index 5400095..00fd09d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
@@ -18,18 +18,19 @@
import android.view.textclassifier.Log
import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController
import javax.inject.Inject
import javax.inject.Singleton
/**
- * The ViewBarn is just a map from [ListEntry] to an instance of an [ExpandableNotificationRow].
+ * The ViewBarn is just a map from [ListEntry] to an instance of an
+ * [ExpandableNotificationRowController].
*/
@Singleton
class NotifViewBarn @Inject constructor() {
- private val rowMap = mutableMapOf<String, ExpandableNotificationRow>()
+ private val rowMap = mutableMapOf<String, ExpandableNotificationRowController>()
- fun requireView(forEntry: ListEntry): ExpandableNotificationRow {
+ fun requireView(forEntry: ListEntry): ExpandableNotificationRowController {
if (DEBUG) {
Log.d(TAG, "requireView: $forEntry.key")
}
@@ -41,11 +42,11 @@
return li
}
- fun registerViewForEntry(entry: ListEntry, view: ExpandableNotificationRow) {
+ fun registerViewForEntry(entry: ListEntry, controller: ExpandableNotificationRowController) {
if (DEBUG) {
Log.d(TAG, "registerViewForEntry: $entry.key")
}
- rowMap[entry.key] = view
+ rowMap[entry.key] = controller
}
fun removeViewForEntry(entry: ListEntry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManager.kt
deleted file mode 100644
index f2e2c39..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManager.kt
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.render
-
-import android.annotation.MainThread
-import android.view.View
-import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY
-import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import javax.inject.Inject
-
-/**
- * A consumer of a Notification tree built by [ShadeListBuilder] which will update the notification
- * presenter with the minimum operations required to make the old tree match the new one
- */
-@MainThread
-class NotifViewManager constructor(
- private val listContainer: NotificationListContainer,
- private val viewBarn: NotifViewBarn,
- private val logger: NotifViewManagerLogger
-) {
- private val rootNode = RootWrapper(listContainer)
- private val rows = mutableMapOf<ListEntry, RowNode>()
-
- fun attach(listBuilder: ShadeListBuilder) {
- listBuilder.setOnRenderListListener(::onNewNotifTree)
- }
-
- private fun onNewNotifTree(tree: List<ListEntry>) {
- // Step 1: Detach all views whose parents have changed
- detachRowsWithModifiedParents()
-
- // Step 2: Attach all new views and reattach all views whose parents changed.
- // Also reorder existing children to match the spec we've received
- val orderChanged = addAndReorderChildren(rootNode, tree)
- if (orderChanged) {
- listContainer.generateChildOrderChangedEvent()
- }
- }
-
- private fun detachRowsWithModifiedParents() {
- val toRemove = mutableListOf<ListEntry>()
- for (row in rows.values) {
- val oldParentEntry = row.nodeParent?.entry
- val newParentEntry = row.entry.parent
-
- if (newParentEntry != oldParentEntry) {
- // If the parent is null, then we should remove the child completely. If not, then
- // the parent merely changed: we'll detach it for now and then attach it to the
- // new parent in step 2.
- val isTransfer = newParentEntry != null
- if (!isTransfer) {
- toRemove.add(row.entry)
- }
-
- if (!isTransfer && !isAttachedToRootEntry(oldParentEntry)) {
- // If our view parent has also been removed (i.e. is no longer attached to the
- // root entry) then we skip removing the child here
- logger.logSkippingDetach(row.entry.key, row.nodeParent?.entry?.key)
- } else {
- logger.logDetachingChild(
- row.entry.key,
- isTransfer,
- oldParentEntry?.key,
- newParentEntry?.key)
- row.nodeParent?.removeChild(row, isTransfer)
- row.nodeParent = null
- }
- }
- }
- rows.keys.removeAll(toRemove)
- }
-
- private fun addAndReorderChildren(parent: ParentNode, childEntries: List<ListEntry>): Boolean {
- var orderChanged = false
- for ((index, entry) in childEntries.withIndex()) {
- val row = getRowNode(entry)
- val currView = parent.getChildViewAt(index)
- if (currView != row.view) {
- when (row.nodeParent) {
- null -> {
- logger.logAttachingChild(row.entry.key, parent.entry.key)
- parent.addChildAt(row, index)
- row.nodeParent = parent
- }
- parent -> {
- logger.logMovingChild(row.entry.key, parent.entry.key, index)
- parent.moveChild(row, index)
- orderChanged = true
- }
- else -> {
- throw IllegalStateException("Child ${row.entry.key} should have parent " +
- "${parent.entry.key} but is actually " +
- "${row.nodeParent?.entry?.key}")
- }
- }
- }
- if (row is GroupWrapper) {
- val childOrderChanged = addAndReorderChildren(row, row.entry.children)
- orderChanged = orderChanged || childOrderChanged
- }
- }
- // TODO: setUntruncatedChildCount
-
- return orderChanged
- }
-
- private fun getRowNode(entry: ListEntry): RowNode {
- return rows.getOrPut(entry) {
- when (entry) {
- is NotificationEntry -> RowWrapper(entry, viewBarn.requireView(entry))
- is GroupEntry ->
- GroupWrapper(
- entry,
- viewBarn.requireView(checkNotNull(entry.summary)),
- listContainer)
- else -> throw RuntimeException(
- "Unexpected entry type for ${entry.key}: ${entry.javaClass}")
- }
- }
- }
-}
-
-class NotifViewManagerBuilder @Inject constructor(
- private val viewBarn: NotifViewBarn,
- private val logger: NotifViewManagerLogger
-) {
- fun build(listContainer: NotificationListContainer): NotifViewManager {
- return NotifViewManager(listContainer, viewBarn, logger)
- }
-}
-
-private fun isAttachedToRootEntry(entry: ListEntry?): Boolean {
- return when (entry) {
- null -> false
- ROOT_ENTRY -> true
- else -> isAttachedToRootEntry(entry.parent)
- }
-}
-
-private interface Node {
- val entry: ListEntry
- val nodeParent: ParentNode?
-}
-
-private interface ParentNode : Node {
- fun getChildViewAt(index: Int): View?
- fun addChildAt(child: RowNode, index: Int)
- fun moveChild(child: RowNode, index: Int)
- fun removeChild(child: RowNode, isTransfer: Boolean)
-}
-
-private interface RowNode : Node {
- val view: ExpandableNotificationRow
- override var nodeParent: ParentNode?
-}
-
-private class RootWrapper(
- private val listContainer: NotificationListContainer
-) : ParentNode {
- override val entry: ListEntry = ROOT_ENTRY
- override val nodeParent: ParentNode? = null
-
- override fun getChildViewAt(index: Int): View? {
- return listContainer.getContainerChildAt(index)
- }
-
- override fun addChildAt(child: RowNode, index: Int) {
- listContainer.addContainerViewAt(child.view, index)
- }
-
- override fun moveChild(child: RowNode, index: Int) {
- listContainer.changeViewPosition(child.view, index)
- }
-
- override fun removeChild(child: RowNode, isTransfer: Boolean) {
- if (isTransfer) {
- listContainer.setChildTransferInProgress(true)
- }
- listContainer.removeContainerView(child.view)
- if (isTransfer) {
- listContainer.setChildTransferInProgress(false)
- }
- }
-}
-
-private class GroupWrapper(
- override val entry: GroupEntry,
- override val view: ExpandableNotificationRow,
- val listContainer: NotificationListContainer
-) : RowNode, ParentNode {
-
- override var nodeParent: ParentNode? = null
-
- override fun getChildViewAt(index: Int): View? {
- return view.getChildNotificationAt(index)
- }
-
- override fun addChildAt(child: RowNode, index: Int) {
- view.addChildNotification(child.view, index)
- listContainer.notifyGroupChildAdded(child.view)
- }
-
- override fun moveChild(child: RowNode, index: Int) {
- view.removeChildNotification(child.view)
- view.addChildNotification(child.view, index)
- }
-
- override fun removeChild(child: RowNode, isTransfer: Boolean) {
- view.removeChildNotification(child.view)
- if (isTransfer) {
- listContainer.notifyGroupChildRemoved(child.view, view)
- }
- }
-}
-
-private class RowWrapper(
- override val entry: NotificationEntry,
- override val view: ExpandableNotificationRow
-) : RowNode {
- override var nodeParent: ParentNode? = null
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
new file mode 100644
index 0000000..e812494
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import android.view.View
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+
+/**
+ * Temporary wrapper around [NotificationListContainer], for use by [ShadeViewDiffer]. Long term,
+ * we should just modify NLC to implement the NodeController interface.
+ */
+class RootNodeController(
+ private val listContainer: NotificationListContainer
+) : NodeController {
+ override val nodeLabel: String = "<root>"
+ override val view: View = listContainer as View
+
+ override fun getChildAt(index: Int): View? {
+ return listContainer.getContainerChildAt(index)
+ }
+
+ override fun getChildCount(): Int {
+ return listContainer.containerChildCount
+ }
+
+ override fun addChildAt(child: NodeController, index: Int) {
+ listContainer.addContainerViewAt(child.view, index)
+ }
+
+ override fun moveChildTo(child: NodeController, index: Int) {
+ listContainer.changeViewPosition(child.view as ExpandableView, index)
+ }
+
+ override fun removeChild(child: NodeController, isTransfer: Boolean) {
+ if (isTransfer) {
+ listContainer.setChildTransferInProgress(true)
+ }
+ listContainer.removeContainerView(child.view)
+ if (isTransfer) {
+ listContainer.setChildTransferInProgress(false)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
new file mode 100644
index 0000000..019520f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import android.annotation.MainThread
+import android.view.View
+import com.android.systemui.util.kotlin.transform
+
+/**
+ * Given a "spec" that describes a "tree" of views, adds and removes views from the
+ * [rootController] and its children until the actual tree matches the spec.
+ *
+ * Every node in the spec tree must specify both a view and its associated [NodeController].
+ * Commands to add/remove/reorder children are sent to the controller. How the controller
+ * interprets these commands is left to its own discretion -- it might add them directly to its
+ * associated view or to some subview container.
+ *
+ * It's possible for nodes to mix "unmanaged" views in alongside managed ones within the same
+ * container. In this case, whenever the differ runs it will move all unmanaged views to the end
+ * of the node's child list.
+ */
+@MainThread
+class ShadeViewDiffer(
+ rootController: NodeController,
+ private val logger: ShadeViewDifferLogger
+) {
+ private val rootNode = ShadeNode(rootController)
+ private val nodes = mutableMapOf(rootController to rootNode)
+ private val views = mutableMapOf<View, ShadeNode>()
+
+ /**
+ * Adds and removes views from the root (and its children) until their structure matches the
+ * provided [spec]. The root node of the spec must match the root controller passed to the
+ * differ's constructor.
+ */
+ fun applySpec(spec: NodeSpec) {
+ val specMap = treeToMap(spec)
+
+ if (spec.controller != rootNode.controller) {
+ throw IllegalArgumentException("Tree root ${spec.controller.nodeLabel} does not " +
+ "match own root at ${rootNode.label}")
+ }
+
+ detachChildren(rootNode, specMap)
+ attachChildren(rootNode, specMap)
+ }
+
+ /**
+ * If [view] is managed by this differ, then returns the label of the view's controller.
+ * Otherwise returns View.toString().
+ *
+ * For debugging purposes.
+ */
+ fun getViewLabel(view: View): String {
+ return views[view]?.label ?: view.toString()
+ }
+
+ private fun detachChildren(
+ parentNode: ShadeNode,
+ specMap: Map<NodeController, NodeSpec>
+ ) {
+ val parentSpec = specMap[parentNode.controller]
+
+ for (i in parentNode.getChildCount() - 1 downTo 0) {
+ val childView = parentNode.getChildAt(i)
+ views[childView]?.let { childNode ->
+ val childSpec = specMap[childNode.controller]
+
+ maybeDetachChild(parentNode, parentSpec, childNode, childSpec)
+
+ if (childNode.controller.getChildCount() > 0) {
+ detachChildren(childNode, specMap)
+ }
+ }
+ }
+ }
+
+ private fun maybeDetachChild(
+ parentNode: ShadeNode,
+ parentSpec: NodeSpec?,
+ childNode: ShadeNode,
+ childSpec: NodeSpec?
+ ) {
+ val newParentNode = transform(childSpec?.parent) { getNode(it) }
+
+ if (newParentNode != parentNode) {
+ val childCompletelyRemoved = newParentNode == null
+
+ if (childCompletelyRemoved) {
+ nodes.remove(childNode.controller)
+ views.remove(childNode.controller.view)
+ }
+
+ if (childCompletelyRemoved && parentSpec == null) {
+ // If both the child and the parent are being removed at the same time, then
+ // keep the child attached to the parent for animation purposes
+ logger.logSkippingDetach(childNode.label, parentNode.label)
+ } else {
+ logger.logDetachingChild(
+ childNode.label,
+ !childCompletelyRemoved,
+ parentNode.label,
+ newParentNode?.label)
+ parentNode.removeChild(childNode, !childCompletelyRemoved)
+ childNode.parent = null
+ }
+ }
+ }
+
+ private fun attachChildren(
+ parentNode: ShadeNode,
+ specMap: Map<NodeController, NodeSpec>
+ ) {
+ val parentSpec = checkNotNull(specMap[parentNode.controller])
+
+ for ((index, childSpec) in parentSpec.children.withIndex()) {
+ val currView = parentNode.getChildAt(index)
+ val childNode = getNode(childSpec)
+
+ if (childNode.view != currView) {
+
+ when (childNode.parent) {
+ null -> {
+ // A new child (either newly created or coming from some other parent)
+ logger.logAttachingChild(childNode.label, parentNode.label)
+ parentNode.addChildAt(childNode, index)
+ childNode.parent = parentNode
+ }
+ parentNode -> {
+ // A pre-existing child, just in the wrong position. Move it into place
+ logger.logMovingChild(childNode.label, parentNode.label, index)
+ parentNode.moveChildTo(childNode, index)
+ }
+ else -> {
+ // Error: child still has a parent. We should have detached it in the
+ // previous step.
+ throw IllegalStateException("Child ${childNode.label} should have " +
+ "parent ${parentNode.label} but is actually " +
+ "${childNode.parent?.label}")
+ }
+ }
+ }
+
+ if (childSpec.children.isNotEmpty()) {
+ attachChildren(childNode, specMap)
+ }
+ }
+ }
+
+ private fun getNode(spec: NodeSpec): ShadeNode {
+ var node = nodes[spec.controller]
+ if (node == null) {
+ node = ShadeNode(spec.controller)
+ nodes[node.controller] = node
+ views[node.view] = node
+ }
+ return node
+ }
+
+ private fun treeToMap(tree: NodeSpec): Map<NodeController, NodeSpec> {
+ val map = mutableMapOf<NodeController, NodeSpec>()
+
+ registerNodes(tree, map)
+
+ return map
+ }
+
+ private fun registerNodes(node: NodeSpec, map: MutableMap<NodeController, NodeSpec>) {
+ if (map.containsKey(node.controller)) {
+ throw RuntimeException("Node ${node.controller.nodeLabel} appears more than once")
+ }
+ map[node.controller] = node
+
+ if (node.children.isNotEmpty()) {
+ for (child in node.children) {
+ registerNodes(child, map)
+ }
+ }
+ }
+}
+
+private class ShadeNode(
+ val controller: NodeController
+) {
+ val view = controller.view
+
+ var parent: ShadeNode? = null
+
+ val label: String
+ get() = controller.nodeLabel
+
+ fun getChildAt(index: Int): View? = controller.getChildAt(index)
+
+ fun getChildCount(): Int = controller.getChildCount()
+
+ fun addChildAt(child: ShadeNode, index: Int) {
+ controller.addChildAt(child.controller, index)
+ }
+
+ fun moveChildTo(child: ShadeNode, index: Int) {
+ controller.moveChildTo(child.controller, index)
+ }
+
+ fun removeChild(child: ShadeNode, isTransfer: Boolean) {
+ controller.removeChild(child.controller, isTransfer)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManagerLogger.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index 3d56126..19e156f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -21,7 +21,7 @@
import com.android.systemui.log.dagger.NotificationLog
import javax.inject.Inject
-class NotifViewManagerLogger @Inject constructor(
+class ShadeViewDifferLogger @Inject constructor(
@NotificationLog private val buffer: LogBuffer
) {
fun logDetachingChild(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
new file mode 100644
index 0000000..201be59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import java.lang.RuntimeException
+import javax.inject.Inject
+
+/**
+ * Responsible for building and applying the "shade node spec": the list (tree) of things that
+ * currently populate the notification shade.
+ */
+class ShadeViewManager constructor(
+ listContainer: NotificationListContainer,
+ logger: ShadeViewDifferLogger,
+ private val viewBarn: NotifViewBarn
+) {
+ private val rootController = RootNodeController(listContainer)
+ private val viewDiffer = ShadeViewDiffer(rootController, logger)
+
+ fun attach(listBuilder: ShadeListBuilder) {
+ listBuilder.setOnRenderListListener(::onNewNotifTree)
+ }
+
+ private fun onNewNotifTree(tree: List<ListEntry>) {
+ viewDiffer.applySpec(buildTree(tree))
+ }
+
+ private fun buildTree(notifList: List<ListEntry>): NodeSpec {
+ val root = NodeSpecImpl(null, rootController)
+
+ for (entry in notifList) {
+ // TODO: Add section header logic here
+ root.children.add(buildNotifNode(entry, root))
+ }
+
+ return root
+ }
+
+ private fun buildNotifNode(entry: ListEntry, parent: NodeSpec): NodeSpec {
+ return when (entry) {
+ is NotificationEntry -> {
+ NodeSpecImpl(parent, viewBarn.requireView(entry))
+ }
+ is GroupEntry -> {
+ val groupNode = NodeSpecImpl(
+ parent,
+ viewBarn.requireView(checkNotNull(entry.summary)))
+
+ for (childEntry in entry.children) {
+ groupNode.children.add(buildNotifNode(childEntry, groupNode))
+ }
+
+ groupNode
+ }
+ else -> {
+ throw RuntimeException("Unexpected entry: $entry")
+ }
+ }
+ }
+}
+
+class ShadeViewManagerFactory @Inject constructor(
+ private val logger: ShadeViewDifferLogger,
+ private val viewBarn: NotifViewBarn
+) {
+ fun create(listContainer: NotificationListContainer): ShadeViewManager {
+ return ShadeViewManager(listContainer, logger, viewBarn)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 86a3271..1dbec6603 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -22,21 +22,27 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.dagger.AppName;
import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.time.SystemClock;
+import java.util.List;
+
import javax.inject.Inject;
import javax.inject.Named;
@@ -44,8 +50,9 @@
* Controller for {@link ExpandableNotificationRow}.
*/
@NotificationRowScope
-public class ExpandableNotificationRowController {
+public class ExpandableNotificationRowController implements NodeController {
private final ExpandableNotificationRow mView;
+ private final NotificationListContainer mListContainer;
private final ActivatableNotificationViewController mActivatableNotificationViewController;
private final NotificationMediaManager mMediaManager;
private final PluginManager mPluginManager;
@@ -72,6 +79,7 @@
@Inject
public ExpandableNotificationRowController(ExpandableNotificationRow view,
+ NotificationListContainer listContainer,
ActivatableNotificationViewController activatableNotificationViewController,
NotificationMediaManager mediaManager, PluginManager pluginManager,
SystemClock clock, @AppName String appName, @NotificationKey String notificationKey,
@@ -86,6 +94,7 @@
OnDismissCallback onDismissCallback, FalsingManager falsingManager,
PeopleNotificationIdentifier peopleNotificationIdentifier) {
mView = view;
+ mListContainer = listContainer;
mActivatableNotificationViewController = activatableNotificationViewController;
mMediaManager = mediaManager;
mPluginManager = pluginManager;
@@ -162,4 +171,52 @@
private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
mNotificationLogger.onExpansionChanged(key, userAction, expanded);
}
+
+ @Override
+ @NonNull
+ public String getNodeLabel() {
+ return mView.getEntry().getKey();
+ }
+
+ @Override
+ @NonNull
+ public View getView() {
+ return mView;
+ }
+
+ @Override
+ public View getChildAt(int index) {
+ return mView.getChildNotificationAt(index);
+ }
+
+ @Override
+ public void addChildAt(NodeController child, int index) {
+ ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView();
+
+ mView.addChildNotification((ExpandableNotificationRow) child.getView());
+ mListContainer.notifyGroupChildAdded(childView);
+ }
+
+ @Override
+ public void moveChildTo(NodeController child, int index) {
+ ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView();
+ mView.removeChildNotification(childView);
+ mView.addChildNotification(childView, index);
+ }
+
+ @Override
+ public void removeChild(NodeController child, boolean isTransfer) {
+ ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView();
+
+ mView.removeChildNotification(childView);
+ if (!isTransfer) {
+ mListContainer.notifyGroupChildRemoved(childView, mView);
+ }
+ }
+
+ @Override
+ public int getChildCount() {
+ final List<ExpandableNotificationRow> mChildren = mView.getAttachedChildren();
+ return mChildren != null ? mChildren.size() : 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
index 28ddf59..becc9a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
@@ -25,6 +25,7 @@
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.StatusBar;
import dagger.Binds;
@@ -55,6 +56,8 @@
Builder notificationEntry(NotificationEntry entry);
@BindsInstance
Builder onExpandClickListener(ExpandableNotificationRow.OnExpandClickListener presenter);
+ @BindsInstance
+ Builder listContainer(NotificationListContainer listContainer);
ExpandableNotificationRowComponent build();
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
new file mode 100644
index 0000000..92c73a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+/**
+ * If [value] is not null, then returns block(value). Otherwise returns null.
+ */
+inline fun <T : Any, R> transform(value: T?, block: (T) -> R): R? = value?.let(block)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
new file mode 100644
index 0000000..bbe92f6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ShadeViewDifferTest extends SysuiTestCase {
+ private ShadeViewDiffer mDiffer;
+
+ private FakeController mRootController = new FakeController(mContext, "RootController");
+ private FakeController mController1 = new FakeController(mContext, "Controller1");
+ private FakeController mController2 = new FakeController(mContext, "Controller2");
+ private FakeController mController3 = new FakeController(mContext, "Controller3");
+ private FakeController mController4 = new FakeController(mContext, "Controller4");
+ private FakeController mController5 = new FakeController(mContext, "Controller5");
+ private FakeController mController6 = new FakeController(mContext, "Controller6");
+ private FakeController mController7 = new FakeController(mContext, "Controller7");
+
+ @Mock
+ ShadeViewDifferLogger mLogger;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mDiffer = new ShadeViewDiffer(mRootController, mLogger);
+ }
+
+ @Test
+ public void testAddInitialViews() {
+ // WHEN a spec is applied to an empty root
+ // THEN the final tree matches the spec
+ applySpecAndCheck(
+ node(mController1),
+ node(mController2,
+ node(mController3),
+ node(mController4)
+ ),
+ node(mController5)
+ );
+ }
+
+ @Test
+ public void testDetachViews() {
+ // GIVEN a preexisting tree of controllers
+ applySpecAndCheck(
+ node(mController1),
+ node(mController2,
+ node(mController3),
+ node(mController4)
+ ),
+ node(mController5)
+ );
+
+ // WHEN the new spec removes nodes
+ // THEN the final tree matches the spec
+ applySpecAndCheck(
+ node(mController5)
+ );
+ }
+
+ @Test
+ public void testReparentChildren() {
+ // GIVEN a preexisting tree of controllers
+ applySpecAndCheck(
+ node(mController1),
+ node(mController2,
+ node(mController3),
+ node(mController4)
+ ),
+ node(mController5)
+ );
+
+ // WHEN the parents of the controllers are all shuffled around
+ // THEN the final tree matches the spec
+ applySpecAndCheck(
+ node(mController1),
+ node(mController4),
+ node(mController3,
+ node(mController2)
+ )
+ );
+ }
+
+ @Test
+ public void testReorderChildren() {
+ // GIVEN a preexisting tree of controllers
+ applySpecAndCheck(
+ node(mController1),
+ node(mController2),
+ node(mController3),
+ node(mController4)
+ );
+
+ // WHEN the children change order
+ // THEN the final tree matches the spec
+ applySpecAndCheck(
+ node(mController3),
+ node(mController2),
+ node(mController4),
+ node(mController1)
+ );
+ }
+
+ @Test
+ public void testRemovedGroupsAreKeptTogether() {
+ // GIVEN a preexisting tree with a group
+ applySpecAndCheck(
+ node(mController1),
+ node(mController2,
+ node(mController3),
+ node(mController4),
+ node(mController5)
+ )
+ );
+
+ // WHEN the new spec removes the entire group
+ applySpecAndCheck(
+ node(mController1)
+ );
+
+ // THEN the group children are still attached to their parent
+ assertEquals(mController2.getView(), mController3.getView().getParent());
+ assertEquals(mController2.getView(), mController4.getView().getParent());
+ assertEquals(mController2.getView(), mController5.getView().getParent());
+ }
+
+ @Test
+ public void testUnmanagedViews() {
+ // GIVEN a preexisting tree of controllers
+ applySpecAndCheck(
+ node(mController1),
+ node(mController2,
+ node(mController3),
+ node(mController4)
+ ),
+ node(mController5)
+ );
+
+ // GIVEN some additional unmanaged views attached to the tree
+ View unmanagedView1 = new View(mContext);
+ View unmanagedView2 = new View(mContext);
+
+ mRootController.getView().addView(unmanagedView1, 1);
+ mController2.getView().addView(unmanagedView2, 0);
+
+ // WHEN a new spec is applied with additional nodes
+ // THEN the final tree matches the spec
+ applySpecAndCheck(
+ node(mController1),
+ node(mController2,
+ node(mController3),
+ node(mController4),
+ node(mController6)
+ ),
+ node(mController5),
+ node(mController7)
+ );
+
+ // THEN the unmanaged views have been pushed to the end of their parents
+ assertEquals(unmanagedView1, mRootController.view.getChildAt(4));
+ assertEquals(unmanagedView2, mController2.view.getChildAt(3));
+ }
+
+ private void applySpecAndCheck(NodeSpec spec) {
+ mDiffer.applySpec(spec);
+ checkMatchesSpec(spec);
+ }
+
+ private void applySpecAndCheck(SpecBuilder... children) {
+ applySpecAndCheck(node(mRootController, children).build());
+ }
+
+ private void checkMatchesSpec(NodeSpec spec) {
+ final NodeController parent = spec.getController();
+ final List<NodeSpec> children = spec.getChildren();
+
+ for (int i = 0; i < children.size(); i++) {
+ NodeSpec childSpec = children.get(i);
+ View view = parent.getChildAt(i);
+
+ assertEquals(
+ "Child " + i + " of parent " + parent.getNodeLabel() + " should be "
+ + childSpec.getController().getNodeLabel() + " but is instead "
+ + (view != null ? mDiffer.getViewLabel(view) : "null"),
+ view,
+ childSpec.getController().getView());
+
+ if (!childSpec.getChildren().isEmpty()) {
+ checkMatchesSpec(childSpec);
+ }
+ }
+ }
+
+ private static class FakeController implements NodeController {
+
+ public final FrameLayout view;
+ private final String mLabel;
+
+ FakeController(Context context, String label) {
+ view = new FrameLayout(context);
+ mLabel = label;
+ }
+
+ @NonNull
+ @Override
+ public String getNodeLabel() {
+ return mLabel;
+ }
+
+ @NonNull
+ @Override
+ public FrameLayout getView() {
+ return view;
+ }
+
+ @Override
+ public int getChildCount() {
+ return view.getChildCount();
+ }
+
+ @Override
+ public View getChildAt(int index) {
+ return view.getChildAt(index);
+ }
+
+ @Override
+ public void addChildAt(@NonNull NodeController child, int index) {
+ view.addView(child.getView(), index);
+ }
+
+ @Override
+ public void moveChildTo(@NonNull NodeController child, int index) {
+ view.removeView(child.getView());
+ view.addView(child.getView(), index);
+ }
+
+ @Override
+ public void removeChild(@NonNull NodeController child, boolean isTransfer) {
+ view.removeView(child.getView());
+ }
+ }
+
+ private static class SpecBuilder {
+ private final NodeController mController;
+ private final SpecBuilder[] mChildren;
+
+ SpecBuilder(NodeController controller, SpecBuilder... children) {
+ mController = controller;
+ mChildren = children;
+ }
+
+ public NodeSpec build() {
+ return build(null);
+ }
+
+ public NodeSpec build(@Nullable NodeSpec parent) {
+ final NodeSpecImpl spec = new NodeSpecImpl(parent, mController);
+ for (SpecBuilder childBuilder : mChildren) {
+ spec.getChildren().add(childBuilder.build(spec));
+ }
+ return spec;
+ }
+ }
+
+ private static SpecBuilder node(NodeController controller, SpecBuilder... children) {
+ return new SpecBuilder(controller, children);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index a90af87..7a0a19b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -221,6 +221,7 @@
.thenAnswer((Answer<ExpandableNotificationRowController>) invocation ->
new ExpandableNotificationRowController(
viewCaptor.getValue(),
+ mListContainer,
mock(ActivatableNotificationViewController.class),
mNotificationMediaManager,
mock(PluginManager.class),
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
index 0038dc2..b78b5d9 100644
--- a/services/core/java/com/android/server/ServiceWatcher.java
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -238,24 +238,13 @@
new PackageMonitor() {
@Override
- public void onPackageUpdateFinished(String packageName, int uid) {
- ServiceWatcher.this.onPackageChanged(packageName);
- }
-
- @Override
- public void onPackageAdded(String packageName, int uid) {
- ServiceWatcher.this.onPackageChanged(packageName);
- }
-
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- ServiceWatcher.this.onPackageChanged(packageName);
- }
-
- @Override
public boolean onPackageChanged(String packageName, int uid, String[] components) {
- ServiceWatcher.this.onPackageChanged(packageName);
- return super.onPackageChanged(packageName, uid, components);
+ return true;
+ }
+
+ @Override
+ public void onSomePackagesChanged() {
+ onBestServiceChanged(false);
}
}.register(mContext, UserHandle.ALL, true, mHandler);
@@ -320,7 +309,7 @@
if (!mTargetService.equals(ServiceInfo.NONE)) {
if (D) {
- Log.i(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService);
+ Log.d(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService);
}
mContext.unbindService(this);
@@ -335,9 +324,7 @@
Preconditions.checkState(mTargetService.component != null);
- if (D) {
- Log.i(TAG, getLogPrefix() + " binding to " + mTargetService);
- }
+ Log.i(TAG, getLogPrefix() + " binding to " + mTargetService);
Intent bindIntent = new Intent(mIntent).setComponent(mTargetService.component);
if (!mContext.bindServiceAsUser(bindIntent, this,
@@ -355,7 +342,7 @@
Preconditions.checkState(mBinder == null);
if (D) {
- Log.i(TAG, getLogPrefix() + " connected to " + component.toShortString());
+ Log.d(TAG, getLogPrefix() + " connected to " + component.toShortString());
}
mBinder = binder;
@@ -379,7 +366,7 @@
}
if (D) {
- Log.i(TAG, getLogPrefix() + " disconnected from " + component.toShortString());
+ Log.d(TAG, getLogPrefix() + " disconnected from " + component.toShortString());
}
mBinder = null;
@@ -392,13 +379,16 @@
public final void onBindingDied(ComponentName component) {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
- if (D) {
- Log.i(TAG, getLogPrefix() + " " + component.toShortString() + " died");
- }
+ Log.i(TAG, getLogPrefix() + " " + component.toShortString() + " died");
onBestServiceChanged(true);
}
+ @Override
+ public final void onNullBinding(ComponentName component) {
+ Log.e(TAG, getLogPrefix() + " " + component.toShortString() + " has null binding");
+ }
+
void onUserSwitched(@UserIdInt int userId) {
mCurrentUserId = userId;
onBestServiceChanged(false);
@@ -410,11 +400,6 @@
}
}
- void onPackageChanged(String packageName) {
- // force a rebind if the changed package was the currently connected package
- onBestServiceChanged(packageName.equals(mTargetService.getPackageName()));
- }
-
/**
* Runs the given function asynchronously if and only if currently connected. Suppresses any
* RemoteException thrown during execution.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 601958e..3e600b7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -98,7 +98,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_WHITELISTS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
@@ -146,7 +145,6 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityManagerInternal;
-import android.app.ActivityManagerProto;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -348,7 +346,6 @@
import com.android.server.ThreadPriorityBooster;
import com.android.server.UserspaceRebootLogger;
import com.android.server.Watchdog;
-import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
import com.android.server.appop.AppOpsService;
import com.android.server.compat.PlatformCompat;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
@@ -489,9 +486,6 @@
// as one line, but close enough for now.
static final int RESERVED_BYTES_PER_LOGCAT_LINE = 100;
- /** If a UID observer takes more than this long, send a WTF. */
- private static final int SLOW_UID_OBSERVER_THRESHOLD_MS = 20;
-
// Necessary ApplicationInfo flags to mark an app as persistent
static final int PERSISTENT_MASK =
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT;
@@ -643,9 +637,6 @@
@VisibleForTesting
long mWaitForNetworkTimeoutMs;
- /** Total # of UID change events dispatched, shown in dumpsys. */
- int mUidChangeDispatchCount;
-
/**
* Uids of apps with current active camera sessions. Access synchronized on
* the IntArray instance itself, and no other locks must be acquired while that
@@ -1013,12 +1004,6 @@
};
/**
- * This is for verifying the UID report flow.
- */
- static final boolean VALIDATE_UID_STATES = true;
- final ActiveUids mValidateUids = new ActiveUids(this, false /* postChangesToAtm */);
-
- /**
* Fingerprints (hashCode()) of stack traces that we've
* already logged DropBox entries for. Guarded by itself. If
* something (rogue user app) forces this over
@@ -1398,69 +1383,6 @@
int foregroundServiceTypes;
}
- static final class UidObserverRegistration {
- final int uid;
- final String pkg;
- final int which;
- final int cutpoint;
-
- /**
- * Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
- * We show it in dumpsys.
- */
- int mSlowDispatchCount;
-
- /** Max time it took for each dispatch. */
- int mMaxDispatchTime;
-
- final SparseIntArray lastProcStates;
-
- // Please keep the enum lists in sync
- private static int[] ORIG_ENUMS = new int[]{
- ActivityManager.UID_OBSERVER_IDLE,
- ActivityManager.UID_OBSERVER_ACTIVE,
- ActivityManager.UID_OBSERVER_GONE,
- ActivityManager.UID_OBSERVER_PROCSTATE,
- };
- private static int[] PROTO_ENUMS = new int[]{
- ActivityManagerProto.UID_OBSERVER_FLAG_IDLE,
- ActivityManagerProto.UID_OBSERVER_FLAG_ACTIVE,
- ActivityManagerProto.UID_OBSERVER_FLAG_GONE,
- ActivityManagerProto.UID_OBSERVER_FLAG_PROCSTATE,
- };
-
- UidObserverRegistration(int _uid, String _pkg, int _which, int _cutpoint) {
- uid = _uid;
- pkg = _pkg;
- which = _which;
- cutpoint = _cutpoint;
- if (cutpoint >= ActivityManager.MIN_PROCESS_STATE) {
- lastProcStates = new SparseIntArray();
- } else {
- lastProcStates = null;
- }
- }
-
- void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- proto.write(UidObserverRegistrationProto.UID, uid);
- proto.write(UidObserverRegistrationProto.PACKAGE, pkg);
- ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, UidObserverRegistrationProto.FLAGS,
- which, ORIG_ENUMS, PROTO_ENUMS);
- proto.write(UidObserverRegistrationProto.CUT_POINT, cutpoint);
- if (lastProcStates != null) {
- final int NI = lastProcStates.size();
- for (int i=0; i<NI; i++) {
- final long pToken = proto.start(UidObserverRegistrationProto.LAST_PROC_STATES);
- proto.write(UidObserverRegistrationProto.ProcState.UID, lastProcStates.keyAt(i));
- proto.write(UidObserverRegistrationProto.ProcState.STATE, lastProcStates.valueAt(i));
- proto.end(pToken);
- }
- }
- proto.end(token);
- }
- }
-
// TODO: Move below 4 members and code to ProcessList
final RemoteCallbackList<IProcessObserver> mProcessObservers = new RemoteCallbackList<>();
ProcessChangeItem[] mActiveProcessChanges = new ProcessChangeItem[5];
@@ -1468,12 +1390,6 @@
final ArrayList<ProcessChangeItem> mPendingProcessChanges = new ArrayList<>();
final ArrayList<ProcessChangeItem> mAvailProcessChanges = new ArrayList<>();
- final RemoteCallbackList<IUidObserver> mUidObservers = new RemoteCallbackList<>();
- UidRecord.ChangeItem[] mActiveUidChanges = new UidRecord.ChangeItem[5];
-
- final ArrayList<UidRecord.ChangeItem> mPendingUidChanges = new ArrayList<>();
- final ArrayList<UidRecord.ChangeItem> mAvailUidChanges = new ArrayList<>();
-
OomAdjObserver mCurOomAdjObserver;
int mCurOomAdjUid;
@@ -1524,6 +1440,8 @@
public final ActivityManagerInternal mInternal;
final ActivityThread mSystemThread;
+ final UidObserverController mUidObserverController;
+
private final class AppDeathRecipient implements IBinder.DeathRecipient {
final ProcessRecord mApp;
final int mPid;
@@ -1569,7 +1487,6 @@
static final int NOTIFY_CLEARTEXT_NETWORK_MSG = 49;
static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 50;
static final int ABORT_DUMPHEAP_MSG = 51;
- static final int DISPATCH_UIDS_CHANGED_UI_MSG = 53;
static final int SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG = 56;
static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 57;
static final int IDLE_UIDS_MSG = 58;
@@ -1698,17 +1615,14 @@
break;
}
case DISPATCH_PROCESS_DIED_UI_MSG: {
+ if (false) { // DO NOT SUBMIT WITH TRUE
+ maybeTriggerWatchdog();
+ }
final int pid = msg.arg1;
final int uid = msg.arg2;
dispatchProcessDied(pid, uid);
break;
}
- case DISPATCH_UIDS_CHANGED_UI_MSG: {
- if (false) { // DO NOT SUBMIT WITH TRUE
- maybeTriggerWatchdog();
- }
- dispatchUidsChanged();
- } break;
case DISPATCH_OOM_ADJ_OBSERVER_MSG: {
dispatchOomAdjObserver((String) msg.obj);
} break;
@@ -2508,6 +2422,7 @@
mConstants = hasHandlerThread
? new ActivityManagerConstants(mContext, this, mHandler) : null;
final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */);
+ mUidObserverController = new UidObserverController(this);
mPlatformCompat = null;
mProcessList = injector.getProcessList(this);
mProcessList.init(this, activeUids, mPlatformCompat);
@@ -2603,6 +2518,7 @@
mCpHelper = new ContentProviderHelper(this, true);
mPackageWatchdog = PackageWatchdog.getInstance(mUiContext);
mAppErrors = new AppErrors(mUiContext, this, mPackageWatchdog);
+ mUidObserverController = new UidObserverController(this);
final File systemDir = SystemServiceManager.ensureSystemDir();
@@ -3419,153 +3335,6 @@
mProcessObservers.finishBroadcast();
}
- @VisibleForTesting
- void dispatchUidsChanged() {
- int N;
- synchronized (this) {
- N = mPendingUidChanges.size();
- if (mActiveUidChanges.length < N) {
- mActiveUidChanges = new UidRecord.ChangeItem[N];
- }
- for (int i=0; i<N; i++) {
- final UidRecord.ChangeItem change = mPendingUidChanges.get(i);
- mActiveUidChanges[i] = change;
- if (change.uidRecord != null) {
- change.uidRecord.pendingChange = null;
- change.uidRecord = null;
- }
- }
- mPendingUidChanges.clear();
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "*** Delivering " + N + " uid changes");
- }
-
- mUidChangeDispatchCount += N;
- int i = mUidObservers.beginBroadcast();
- while (i > 0) {
- i--;
- dispatchUidsChangedForObserver(mUidObservers.getBroadcastItem(i),
- (UidObserverRegistration) mUidObservers.getBroadcastCookie(i), N);
- }
- mUidObservers.finishBroadcast();
-
- if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
- for (int j = 0; j < N; ++j) {
- final UidRecord.ChangeItem item = mActiveUidChanges[j];
- if ((item.change & UidRecord.CHANGE_GONE) != 0) {
- mValidateUids.remove(item.uid);
- } else {
- UidRecord validateUid = mValidateUids.get(item.uid);
- if (validateUid == null) {
- validateUid = new UidRecord(item.uid);
- mValidateUids.put(item.uid, validateUid);
- }
- if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
- validateUid.idle = true;
- } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
- validateUid.idle = false;
- }
- validateUid.setCurProcState(validateUid.setProcState = item.processState);
- validateUid.curCapability = validateUid.setCapability = item.capability;
- validateUid.lastDispatchedProcStateSeq = item.procStateSeq;
- }
- }
- }
-
- synchronized (this) {
- for (int j = 0; j < N; j++) {
- mAvailUidChanges.add(mActiveUidChanges[j]);
- }
- }
- }
-
- private void dispatchUidsChangedForObserver(IUidObserver observer,
- UidObserverRegistration reg, int changesSize) {
- if (observer == null) {
- return;
- }
- try {
- for (int j = 0; j < changesSize; j++) {
- UidRecord.ChangeItem item = mActiveUidChanges[j];
- final int change = item.change;
- if (change == UidRecord.CHANGE_PROCSTATE &&
- (reg.which & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) {
- // No-op common case: no significant change, the observer is not
- // interested in all proc state changes.
- continue;
- }
- final long start = SystemClock.uptimeMillis();
- if ((change & UidRecord.CHANGE_IDLE) != 0) {
- if ((reg.which & ActivityManager.UID_OBSERVER_IDLE) != 0) {
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "UID idle uid=" + item.uid);
- observer.onUidIdle(item.uid, item.ephemeral);
- }
- } else if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
- if ((reg.which & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "UID active uid=" + item.uid);
- observer.onUidActive(item.uid);
- }
- }
- if ((reg.which & ActivityManager.UID_OBSERVER_CACHED) != 0) {
- if ((change & UidRecord.CHANGE_CACHED) != 0) {
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "UID cached uid=" + item.uid);
- observer.onUidCachedChanged(item.uid, true);
- } else if ((change & UidRecord.CHANGE_UNCACHED) != 0) {
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "UID active uid=" + item.uid);
- observer.onUidCachedChanged(item.uid, false);
- }
- }
- if ((change & UidRecord.CHANGE_GONE) != 0) {
- if ((reg.which & ActivityManager.UID_OBSERVER_GONE) != 0) {
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "UID gone uid=" + item.uid);
- observer.onUidGone(item.uid, item.ephemeral);
- }
- if (reg.lastProcStates != null) {
- reg.lastProcStates.delete(item.uid);
- }
- } else {
- if ((reg.which & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "UID CHANGED uid=" + item.uid
- + ": " + item.processState + ": " + item.capability);
- boolean doReport = true;
- if (reg.cutpoint >= ActivityManager.MIN_PROCESS_STATE) {
- final int lastState = reg.lastProcStates.get(item.uid,
- ActivityManager.PROCESS_STATE_UNKNOWN);
- if (lastState != ActivityManager.PROCESS_STATE_UNKNOWN) {
- final boolean lastAboveCut = lastState <= reg.cutpoint;
- final boolean newAboveCut = item.processState <= reg.cutpoint;
- doReport = lastAboveCut != newAboveCut;
- } else {
- doReport = item.processState != PROCESS_STATE_NONEXISTENT;
- }
- }
- if (doReport) {
- if (reg.lastProcStates != null) {
- reg.lastProcStates.put(item.uid, item.processState);
- }
- observer.onUidStateChanged(item.uid, item.processState,
- item.procStateSeq, item.capability);
- }
- }
- }
- final int duration = (int) (SystemClock.uptimeMillis() - start);
- if (reg.mMaxDispatchTime < duration) {
- reg.mMaxDispatchTime = duration;
- }
- if (duration >= SLOW_UID_OBSERVER_THRESHOLD_MS) {
- reg.mSlowDispatchCount++;
- }
- }
- } catch (RemoteException e) {
- }
- }
-
void dispatchOomAdjObserver(String msg) {
OomAdjObserver observer;
synchronized (this) {
@@ -7502,15 +7271,15 @@
"registerUidObserver");
}
synchronized (this) {
- mUidObservers.register(observer, new UidObserverRegistration(Binder.getCallingUid(),
- callingPackage, which, cutpoint));
+ mUidObserverController.register(observer, which, cutpoint, callingPackage,
+ Binder.getCallingUid());
}
}
@Override
public void unregisterUidObserver(IUidObserver observer) {
synchronized (this) {
- mUidObservers.unregister(observer);
+ mUidObserverController.unregister(observer);
}
}
@@ -10129,9 +9898,9 @@
}
if (dumpAll) {
- if (mValidateUids.size() > 0) {
- if (dumpUids(pw, dumpPackage, dumpAppId, mValidateUids, "UID validation:",
- needSep)) {
+ if (mUidObserverController.mValidateUids.size() > 0) {
+ if (dumpUids(pw, dumpPackage, dumpAppId, mUidObserverController.mValidateUids,
+ "UID validation:", needSep)) {
needSep = true;
}
}
@@ -10263,45 +10032,8 @@
}
}
if (dumpAll) {
- final int NI = mUidObservers.getRegisteredCallbackCount();
- boolean printed = false;
- for (int i=0; i<NI; i++) {
- final UidObserverRegistration reg = (UidObserverRegistration)
- mUidObservers.getRegisteredCallbackCookie(i);
- if (dumpPackage == null || dumpPackage.equals(reg.pkg)) {
- if (!printed) {
- pw.println(" mUidObservers:");
- printed = true;
- }
- pw.print(" "); UserHandle.formatUid(pw, reg.uid);
- pw.print(" "); pw.print(reg.pkg);
- final IUidObserver observer = mUidObservers.getRegisteredCallbackItem(i);
- pw.print(" "); pw.print(observer.getClass().getTypeName()); pw.print(":");
- if ((reg.which&ActivityManager.UID_OBSERVER_IDLE) != 0) {
- pw.print(" IDLE");
- }
- if ((reg.which&ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
- pw.print(" ACT" );
- }
- if ((reg.which&ActivityManager.UID_OBSERVER_GONE) != 0) {
- pw.print(" GONE");
- }
- if ((reg.which&ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
- pw.print(" STATE");
- pw.print(" (cut="); pw.print(reg.cutpoint);
- pw.print(")");
- }
- pw.println();
- if (reg.lastProcStates != null) {
- final int NJ = reg.lastProcStates.size();
- for (int j=0; j<NJ; j++) {
- pw.print(" Last ");
- UserHandle.formatUid(pw, reg.lastProcStates.keyAt(j));
- pw.print(": "); pw.println(reg.lastProcStates.valueAt(j));
- }
- }
- }
- }
+ mUidObserverController.dump(pw, dumpPackage);
+
pw.println(" mDeviceIdleWhitelist=" + Arrays.toString(mDeviceIdleWhitelist));
pw.println(" mDeviceIdleExceptIdleWhitelist="
+ Arrays.toString(mDeviceIdleExceptIdleWhitelist));
@@ -10429,25 +10161,6 @@
pw.print(" mLowRamSinceLastIdle=");
TimeUtils.formatDuration(getLowRamTimeSinceIdle(now), pw);
pw.println();
- pw.println();
- pw.print(" mUidChangeDispatchCount=");
- pw.print(mUidChangeDispatchCount);
- pw.println();
-
- pw.println(" Slow UID dispatches:");
- final int N = mUidObservers.beginBroadcast();
- for (int i = 0; i < N; i++) {
- UidObserverRegistration r =
- (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
- pw.print(" ");
- pw.print(mUidObservers.getBroadcastItem(i).getClass().getTypeName());
- pw.print(": ");
- pw.print(r.mSlowDispatchCount);
- pw.print(" / Max ");
- pw.print(r.mMaxDispatchTime);
- pw.println("ms");
- }
- mUidObservers.finishBroadcast();
pw.println();
pw.println(" ServiceManager statistics:");
@@ -10515,8 +10228,8 @@
uidRec.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.ACTIVE_UIDS);
}
- for (int i = 0; i < mValidateUids.size(); i++) {
- UidRecord uidRec = mValidateUids.valueAt(i);
+ for (int i = 0; i < mUidObserverController.mValidateUids.size(); i++) {
+ UidRecord uidRec = mUidObserverController.mValidateUids.valueAt(i);
if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
continue;
}
@@ -10601,14 +10314,7 @@
ActivityManagerServiceDumpProcessesProto.USER_CONTROLLER);
}
- final int NI = mUidObservers.getRegisteredCallbackCount();
- for (int i=0; i<NI; i++) {
- final UidObserverRegistration reg = (UidObserverRegistration)
- mUidObservers.getRegisteredCallbackCookie(i);
- if (dumpPackage == null || dumpPackage.equals(reg.pkg)) {
- reg.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.UID_OBSERVERS);
- }
- }
+ mUidObserverController.dumpDebug(proto, dumpPackage);
for (int v : mDeviceIdleWhitelist) {
proto.write(ActivityManagerServiceDumpProcessesProto.DEVICE_IDLE_WHITELIST, v);
@@ -16318,101 +16024,6 @@
}
}
- private boolean isEphemeralLocked(int uid) {
- String packages[] = mContext.getPackageManager().getPackagesForUid(uid);
- if (packages == null || packages.length != 1) { // Ephemeral apps cannot share uid
- return false;
- }
- return getPackageManagerInternalLocked().isPackageEphemeral(UserHandle.getUserId(uid),
- packages[0]);
- }
-
- @VisibleForTesting
- final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
- final UidRecord.ChangeItem pendingChange;
- if (uidRec == null || uidRec.pendingChange == null) {
- if (mPendingUidChanges.size() == 0) {
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "*** Enqueueing dispatch uid changed!");
- mUiHandler.obtainMessage(DISPATCH_UIDS_CHANGED_UI_MSG).sendToTarget();
- }
- final int NA = mAvailUidChanges.size();
- if (NA > 0) {
- pendingChange = mAvailUidChanges.remove(NA-1);
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "Retrieving available item: " + pendingChange);
- } else {
- pendingChange = new UidRecord.ChangeItem();
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "Allocating new item: " + pendingChange);
- }
- if (uidRec != null) {
- uidRec.pendingChange = pendingChange;
- if ((change & UidRecord.CHANGE_GONE) != 0 && !uidRec.idle) {
- // If this uid is going away, and we haven't yet reported it is gone,
- // then do so now.
- change |= UidRecord.CHANGE_IDLE;
- }
- } else if (uid < 0) {
- throw new IllegalArgumentException("No UidRecord or uid");
- }
- pendingChange.uidRecord = uidRec;
- pendingChange.uid = uidRec != null ? uidRec.uid : uid;
- mPendingUidChanges.add(pendingChange);
- } else {
- pendingChange = uidRec.pendingChange;
- // If there is no change in idle or active state, then keep whatever was pending.
- if ((change & (UidRecord.CHANGE_IDLE | UidRecord.CHANGE_ACTIVE)) == 0) {
- change |= (pendingChange.change & (UidRecord.CHANGE_IDLE
- | UidRecord.CHANGE_ACTIVE));
- }
- // If there is no change in cached or uncached state, then keep whatever was pending.
- if ((change & (UidRecord.CHANGE_CACHED | UidRecord.CHANGE_UNCACHED)) == 0) {
- change |= (pendingChange.change & (UidRecord.CHANGE_CACHED
- | UidRecord.CHANGE_UNCACHED));
- }
- // If this is a report of the UID being gone, then we shouldn't keep any previous
- // report of it being active or cached. (That is, a gone uid is never active,
- // and never cached.)
- if ((change & UidRecord.CHANGE_GONE) != 0) {
- change &= ~(UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_CACHED);
- if (!uidRec.idle) {
- // If this uid is going away, and we haven't yet reported it is gone,
- // then do so now.
- change |= UidRecord.CHANGE_IDLE;
- }
- }
- }
- pendingChange.change = change;
- pendingChange.processState = uidRec != null ? uidRec.setProcState : PROCESS_STATE_NONEXISTENT;
- pendingChange.capability = uidRec != null ? uidRec.setCapability : 0;
- pendingChange.ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
- pendingChange.procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
- if (uidRec != null) {
- uidRec.lastReportedChange = change;
- uidRec.updateLastDispatchedProcStateSeq(change);
- }
-
- // Directly update the power manager, since we sit on top of it and it is critical
- // it be kept in sync (so wake locks will be held as soon as appropriate).
- if (mLocalPowerManager != null) {
- // TO DO: dispatch cached/uncached changes here, so we don't need to report
- // all proc state changes.
- if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
- mLocalPowerManager.uidActive(pendingChange.uid);
- }
- if ((change & UidRecord.CHANGE_IDLE) != 0) {
- mLocalPowerManager.uidIdle(pendingChange.uid);
- }
- if ((change & UidRecord.CHANGE_GONE) != 0) {
- mLocalPowerManager.uidGone(pendingChange.uid);
- } else {
- mLocalPowerManager.updateUidProcState(pendingChange.uid,
- pendingChange.processState);
- }
- }
- }
-
final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
synchronized (mProcessStats.mLock) {
if (proc.thread != null && proc.baseProcessTracker != null) {
@@ -16833,7 +16444,7 @@
@GuardedBy("this")
final void doStopUidLocked(int uid, final UidRecord uidRec) {
mServices.stopInBackgroundLocked(uid);
- enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
+ mUidObserverController.enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
}
/**
@@ -18494,7 +18105,8 @@
Slog.w(TAG_NETWORK, "Total time waited for network rules to get updated: "
+ totalTime + ". Uid: " + callingUid + " procStateSeq: "
+ procStateSeq + " UidRec: " + record
- + " validateUidRec: " + mValidateUids.get(callingUid));
+ + " validateUidRec: "
+ + mUidObserverController.mValidateUids.get(callingUid));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index bf15f1737..bad042c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -987,7 +987,7 @@
uidRec.setWhitelist = uidRec.curWhitelist;
uidRec.setIdle = uidRec.idle;
mService.mAtmInternal.onUidProcStateChanged(uidRec.uid, uidRec.setProcState);
- mService.enqueueUidChangeLocked(uidRec, -1, uidChange);
+ mService.mUidObserverController.enqueueUidChangeLocked(uidRec, -1, uidChange);
mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState(),
uidRec.curCapability);
if (uidRec.foregroundServices) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 5721fb7..2e8660e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2960,7 +2960,8 @@
// No more processes using this uid, tell clients it is gone.
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
"No more processes in " + uidRecord);
- mService.enqueueUidChangeLocked(uidRecord, -1, UidRecord.CHANGE_GONE);
+ mService.mUidObserverController.enqueueUidChangeLocked(uidRecord, -1,
+ UidRecord.CHANGE_GONE);
EventLogTags.writeAmUidStopped(uid);
mActiveUids.remove(uid);
mService.noteUidProcessState(uid, ActivityManager.PROCESS_STATE_NONEXISTENT,
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
new file mode 100644
index 0000000..4c4dd8b
--- /dev/null
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerProto;
+import android.app.IUidObserver;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class UidObserverController {
+ private final ActivityManagerService mService;
+ final RemoteCallbackList<IUidObserver> mUidObservers = new RemoteCallbackList<>();
+
+ UidRecord.ChangeItem[] mActiveUidChanges = new UidRecord.ChangeItem[5];
+ final ArrayList<UidRecord.ChangeItem> mPendingUidChanges = new ArrayList<>();
+ final ArrayList<UidRecord.ChangeItem> mAvailUidChanges = new ArrayList<>();
+
+ /** Total # of UID change events dispatched, shown in dumpsys. */
+ int mUidChangeDispatchCount;
+
+ /** If a UID observer takes more than this long, send a WTF. */
+ private static final int SLOW_UID_OBSERVER_THRESHOLD_MS = 20;
+
+ /**
+ * This is for verifying the UID report flow.
+ */
+ static final boolean VALIDATE_UID_STATES = true;
+ final ActiveUids mValidateUids;
+
+ UidObserverController(ActivityManagerService service) {
+ mService = service;
+ mValidateUids = new ActiveUids(mService, false /* postChangesToAtm */);
+ }
+
+ @GuardedBy("mService")
+ void register(IUidObserver observer, int which, int cutpoint, String callingPackage,
+ int callingUid) {
+ mUidObservers.register(observer, new UidObserverRegistration(callingUid,
+ callingPackage, which, cutpoint));
+ }
+
+ @GuardedBy("mService")
+ void unregister(IUidObserver observer) {
+ mUidObservers.unregister(observer);
+ }
+
+ @GuardedBy("mService")
+ final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
+ final UidRecord.ChangeItem pendingChange;
+ if (uidRec == null || uidRec.pendingChange == null) {
+ if (mPendingUidChanges.size() == 0) {
+ if (DEBUG_UID_OBSERVERS) {
+ Slog.i(TAG_UID_OBSERVERS, "*** Enqueueing dispatch uid changed!");
+ }
+ mService.mUiHandler.post(this::dispatchUidsChanged);
+ }
+ final int NA = mAvailUidChanges.size();
+ if (NA > 0) {
+ pendingChange = mAvailUidChanges.remove(NA-1);
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "Retrieving available item: " + pendingChange);
+ } else {
+ pendingChange = new UidRecord.ChangeItem();
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "Allocating new item: " + pendingChange);
+ }
+ if (uidRec != null) {
+ uidRec.pendingChange = pendingChange;
+ if ((change & UidRecord.CHANGE_GONE) != 0 && !uidRec.idle) {
+ // If this uid is going away, and we haven't yet reported it is gone,
+ // then do so now.
+ change |= UidRecord.CHANGE_IDLE;
+ }
+ } else if (uid < 0) {
+ throw new IllegalArgumentException("No UidRecord or uid");
+ }
+ pendingChange.uidRecord = uidRec;
+ pendingChange.uid = uidRec != null ? uidRec.uid : uid;
+ mPendingUidChanges.add(pendingChange);
+ } else {
+ pendingChange = uidRec.pendingChange;
+ // If there is no change in idle or active state, then keep whatever was pending.
+ if ((change & (UidRecord.CHANGE_IDLE | UidRecord.CHANGE_ACTIVE)) == 0) {
+ change |= (pendingChange.change & (UidRecord.CHANGE_IDLE
+ | UidRecord.CHANGE_ACTIVE));
+ }
+ // If there is no change in cached or uncached state, then keep whatever was pending.
+ if ((change & (UidRecord.CHANGE_CACHED | UidRecord.CHANGE_UNCACHED)) == 0) {
+ change |= (pendingChange.change & (UidRecord.CHANGE_CACHED
+ | UidRecord.CHANGE_UNCACHED));
+ }
+ // If this is a report of the UID being gone, then we shouldn't keep any previous
+ // report of it being active or cached. (That is, a gone uid is never active,
+ // and never cached.)
+ if ((change & UidRecord.CHANGE_GONE) != 0) {
+ change &= ~(UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_CACHED);
+ if (!uidRec.idle) {
+ // If this uid is going away, and we haven't yet reported it is gone,
+ // then do so now.
+ change |= UidRecord.CHANGE_IDLE;
+ }
+ }
+ }
+ pendingChange.change = change;
+ pendingChange.processState = uidRec != null ? uidRec.setProcState : PROCESS_STATE_NONEXISTENT;
+ pendingChange.capability = uidRec != null ? uidRec.setCapability : 0;
+ pendingChange.ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
+ pendingChange.procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
+ if (uidRec != null) {
+ uidRec.lastReportedChange = change;
+ uidRec.updateLastDispatchedProcStateSeq(change);
+ }
+
+ // Directly update the power manager, since we sit on top of it and it is critical
+ // it be kept in sync (so wake locks will be held as soon as appropriate).
+ if (mService.mLocalPowerManager != null) {
+ // TO DO: dispatch cached/uncached changes here, so we don't need to report
+ // all proc state changes.
+ if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
+ mService.mLocalPowerManager.uidActive(pendingChange.uid);
+ }
+ if ((change & UidRecord.CHANGE_IDLE) != 0) {
+ mService.mLocalPowerManager.uidIdle(pendingChange.uid);
+ }
+ if ((change & UidRecord.CHANGE_GONE) != 0) {
+ mService.mLocalPowerManager.uidGone(pendingChange.uid);
+ } else {
+ mService.mLocalPowerManager.updateUidProcState(pendingChange.uid,
+ pendingChange.processState);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void dispatchUidsChanged() {
+ int N;
+ synchronized (mService) {
+ N = mPendingUidChanges.size();
+ if (mActiveUidChanges.length < N) {
+ mActiveUidChanges = new UidRecord.ChangeItem[N];
+ }
+ for (int i=0; i<N; i++) {
+ final UidRecord.ChangeItem change = mPendingUidChanges.get(i);
+ mActiveUidChanges[i] = change;
+ if (change.uidRecord != null) {
+ change.uidRecord.pendingChange = null;
+ change.uidRecord = null;
+ }
+ }
+ mPendingUidChanges.clear();
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "*** Delivering " + N + " uid changes");
+ }
+
+ mUidChangeDispatchCount += N;
+ int i = mUidObservers.beginBroadcast();
+ while (i > 0) {
+ i--;
+ dispatchUidsChangedForObserver(mUidObservers.getBroadcastItem(i),
+ (UidObserverRegistration) mUidObservers.getBroadcastCookie(i), N);
+ }
+ mUidObservers.finishBroadcast();
+
+ if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
+ for (int j = 0; j < N; ++j) {
+ final UidRecord.ChangeItem item = mActiveUidChanges[j];
+ if ((item.change & UidRecord.CHANGE_GONE) != 0) {
+ mValidateUids.remove(item.uid);
+ } else {
+ UidRecord validateUid = mValidateUids.get(item.uid);
+ if (validateUid == null) {
+ validateUid = new UidRecord(item.uid);
+ mValidateUids.put(item.uid, validateUid);
+ }
+ if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
+ validateUid.idle = true;
+ } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
+ validateUid.idle = false;
+ }
+ validateUid.setCurProcState(validateUid.setProcState = item.processState);
+ validateUid.curCapability = validateUid.setCapability = item.capability;
+ validateUid.lastDispatchedProcStateSeq = item.procStateSeq;
+ }
+ }
+ }
+
+ synchronized (mService) {
+ for (int j = 0; j < N; j++) {
+ mAvailUidChanges.add(mActiveUidChanges[j]);
+ }
+ }
+ }
+
+ private void dispatchUidsChangedForObserver(IUidObserver observer,
+ UidObserverRegistration reg, int changesSize) {
+ if (observer == null) {
+ return;
+ }
+ try {
+ for (int j = 0; j < changesSize; j++) {
+ UidRecord.ChangeItem item = mActiveUidChanges[j];
+ final int change = item.change;
+ if (change == UidRecord.CHANGE_PROCSTATE &&
+ (reg.which & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) {
+ // No-op common case: no significant change, the observer is not
+ // interested in all proc state changes.
+ continue;
+ }
+ final long start = SystemClock.uptimeMillis();
+ if ((change & UidRecord.CHANGE_IDLE) != 0) {
+ if ((reg.which & ActivityManager.UID_OBSERVER_IDLE) != 0) {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "UID idle uid=" + item.uid);
+ observer.onUidIdle(item.uid, item.ephemeral);
+ }
+ } else if ((change & UidRecord.CHANGE_ACTIVE) != 0) {
+ if ((reg.which & ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "UID active uid=" + item.uid);
+ observer.onUidActive(item.uid);
+ }
+ }
+ if ((reg.which & ActivityManager.UID_OBSERVER_CACHED) != 0) {
+ if ((change & UidRecord.CHANGE_CACHED) != 0) {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "UID cached uid=" + item.uid);
+ observer.onUidCachedChanged(item.uid, true);
+ } else if ((change & UidRecord.CHANGE_UNCACHED) != 0) {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "UID active uid=" + item.uid);
+ observer.onUidCachedChanged(item.uid, false);
+ }
+ }
+ if ((change & UidRecord.CHANGE_GONE) != 0) {
+ if ((reg.which & ActivityManager.UID_OBSERVER_GONE) != 0) {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "UID gone uid=" + item.uid);
+ observer.onUidGone(item.uid, item.ephemeral);
+ }
+ if (reg.lastProcStates != null) {
+ reg.lastProcStates.delete(item.uid);
+ }
+ } else {
+ if ((reg.which & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "UID CHANGED uid=" + item.uid
+ + ": " + item.processState + ": " + item.capability);
+ boolean doReport = true;
+ if (reg.cutpoint >= ActivityManager.MIN_PROCESS_STATE) {
+ final int lastState = reg.lastProcStates.get(item.uid,
+ ActivityManager.PROCESS_STATE_UNKNOWN);
+ if (lastState != ActivityManager.PROCESS_STATE_UNKNOWN) {
+ final boolean lastAboveCut = lastState <= reg.cutpoint;
+ final boolean newAboveCut = item.processState <= reg.cutpoint;
+ doReport = lastAboveCut != newAboveCut;
+ } else {
+ doReport = item.processState != PROCESS_STATE_NONEXISTENT;
+ }
+ }
+ if (doReport) {
+ if (reg.lastProcStates != null) {
+ reg.lastProcStates.put(item.uid, item.processState);
+ }
+ observer.onUidStateChanged(item.uid, item.processState,
+ item.procStateSeq, item.capability);
+ }
+ }
+ }
+ final int duration = (int) (SystemClock.uptimeMillis() - start);
+ if (reg.mMaxDispatchTime < duration) {
+ reg.mMaxDispatchTime = duration;
+ }
+ if (duration >= SLOW_UID_OBSERVER_THRESHOLD_MS) {
+ reg.mSlowDispatchCount++;
+ }
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
+ private boolean isEphemeralLocked(int uid) {
+ String packages[] = mService.mContext.getPackageManager().getPackagesForUid(uid);
+ if (packages == null || packages.length != 1) { // Ephemeral apps cannot share uid
+ return false;
+ }
+ return mService.getPackageManagerInternalLocked().isPackageEphemeral(
+ UserHandle.getUserId(uid), packages[0]);
+ }
+
+ @GuardedBy("mService")
+ void dump(PrintWriter pw, String dumpPackage) {
+ final int NI = mUidObservers.getRegisteredCallbackCount();
+ boolean printed = false;
+ for (int i=0; i<NI; i++) {
+ final UidObserverRegistration reg = (UidObserverRegistration)
+ mUidObservers.getRegisteredCallbackCookie(i);
+ if (dumpPackage == null || dumpPackage.equals(reg.pkg)) {
+ if (!printed) {
+ pw.println(" mUidObservers:");
+ printed = true;
+ }
+ pw.print(" "); UserHandle.formatUid(pw, reg.uid);
+ pw.print(" "); pw.print(reg.pkg);
+ final IUidObserver observer = mUidObservers.getRegisteredCallbackItem(i);
+ pw.print(" "); pw.print(observer.getClass().getTypeName()); pw.print(":");
+ if ((reg.which&ActivityManager.UID_OBSERVER_IDLE) != 0) {
+ pw.print(" IDLE");
+ }
+ if ((reg.which&ActivityManager.UID_OBSERVER_ACTIVE) != 0) {
+ pw.print(" ACT" );
+ }
+ if ((reg.which&ActivityManager.UID_OBSERVER_GONE) != 0) {
+ pw.print(" GONE");
+ }
+ if ((reg.which&ActivityManager.UID_OBSERVER_PROCSTATE) != 0) {
+ pw.print(" STATE");
+ pw.print(" (cut="); pw.print(reg.cutpoint);
+ pw.print(")");
+ }
+ pw.println();
+ if (reg.lastProcStates != null) {
+ final int NJ = reg.lastProcStates.size();
+ for (int j=0; j<NJ; j++) {
+ pw.print(" Last ");
+ UserHandle.formatUid(pw, reg.lastProcStates.keyAt(j));
+ pw.print(": "); pw.println(reg.lastProcStates.valueAt(j));
+ }
+ }
+ }
+ }
+
+ pw.println();
+ pw.print(" mUidChangeDispatchCount=");
+ pw.print(mUidChangeDispatchCount);
+ pw.println();
+ pw.println(" Slow UID dispatches:");
+ final int N = mUidObservers.beginBroadcast();
+ for (int i = 0; i < N; i++) {
+ UidObserverRegistration r =
+ (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
+ pw.print(" ");
+ pw.print(mUidObservers.getBroadcastItem(i).getClass().getTypeName());
+ pw.print(": ");
+ pw.print(r.mSlowDispatchCount);
+ pw.print(" / Max ");
+ pw.print(r.mMaxDispatchTime);
+ pw.println("ms");
+ }
+ mUidObservers.finishBroadcast();
+ }
+
+ @GuardedBy("mService")
+ void dumpDebug(ProtoOutputStream proto, String dumpPackage) {
+ final int NI = mUidObservers.getRegisteredCallbackCount();
+ for (int i=0; i<NI; i++) {
+ final UidObserverRegistration reg = (UidObserverRegistration)
+ mUidObservers.getRegisteredCallbackCookie(i);
+ if (dumpPackage == null || dumpPackage.equals(reg.pkg)) {
+ reg.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.UID_OBSERVERS);
+ }
+ }
+ }
+
+ static final class UidObserverRegistration {
+ final int uid;
+ final String pkg;
+ final int which;
+ final int cutpoint;
+
+ /**
+ * Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
+ * We show it in dumpsys.
+ */
+ int mSlowDispatchCount;
+
+ /** Max time it took for each dispatch. */
+ int mMaxDispatchTime;
+
+ final SparseIntArray lastProcStates;
+
+ // Please keep the enum lists in sync
+ private static int[] ORIG_ENUMS = new int[]{
+ ActivityManager.UID_OBSERVER_IDLE,
+ ActivityManager.UID_OBSERVER_ACTIVE,
+ ActivityManager.UID_OBSERVER_GONE,
+ ActivityManager.UID_OBSERVER_PROCSTATE,
+ };
+ private static int[] PROTO_ENUMS = new int[]{
+ ActivityManagerProto.UID_OBSERVER_FLAG_IDLE,
+ ActivityManagerProto.UID_OBSERVER_FLAG_ACTIVE,
+ ActivityManagerProto.UID_OBSERVER_FLAG_GONE,
+ ActivityManagerProto.UID_OBSERVER_FLAG_PROCSTATE,
+ };
+
+ UidObserverRegistration(int _uid, String _pkg, int _which, int _cutpoint) {
+ uid = _uid;
+ pkg = _pkg;
+ which = _which;
+ cutpoint = _cutpoint;
+ if (cutpoint >= ActivityManager.MIN_PROCESS_STATE) {
+ lastProcStates = new SparseIntArray();
+ } else {
+ lastProcStates = null;
+ }
+ }
+
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(UidObserverRegistrationProto.UID, uid);
+ proto.write(UidObserverRegistrationProto.PACKAGE, pkg);
+ ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, UidObserverRegistrationProto.FLAGS,
+ which, ORIG_ENUMS, PROTO_ENUMS);
+ proto.write(UidObserverRegistrationProto.CUT_POINT, cutpoint);
+ if (lastProcStates != null) {
+ final int NI = lastProcStates.size();
+ for (int i=0; i<NI; i++) {
+ final long pToken = proto.start(UidObserverRegistrationProto.LAST_PROC_STATES);
+ proto.write(UidObserverRegistrationProto.ProcState.UID,
+ lastProcStates.keyAt(i));
+ proto.write(UidObserverRegistrationProto.ProcState.STATE,
+ lastProcStates.valueAt(i));
+ proto.end(pToken);
+ }
+ }
+ proto.end(token);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java
index c3532a8..05aa315 100644
--- a/services/core/java/com/android/server/location/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/LocationProviderManager.java
@@ -229,7 +229,7 @@
// we cache these values because checking/calculating on the fly is more expensive
private boolean mPermitted;
private boolean mForeground;
- @Nullable private LocationRequest mProviderLocationRequest;
+ private LocationRequest mProviderLocationRequest;
private boolean mIsUsingHighPower;
protected Registration(LocationRequest request, CallerIdentity identity,
@@ -244,6 +244,8 @@
} else {
mWorkSource = identity.addToWorkSource(null);
}
+
+ mProviderLocationRequest = super.getRequest();
}
@GuardedBy("mLock")
@@ -313,7 +315,7 @@
@Override
public final LocationRequest getRequest() {
- return Objects.requireNonNull(mProviderLocationRequest);
+ return mProviderLocationRequest;
}
public final boolean isForeground() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2d052da..52928dc 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7758,6 +7758,13 @@
@VisibleForTesting
void updateUriPermissions(@Nullable NotificationRecord newRecord,
@Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId) {
+ updateUriPermissions(newRecord, oldRecord, targetPkg, targetUserId, false);
+ }
+
+ @VisibleForTesting
+ void updateUriPermissions(@Nullable NotificationRecord newRecord,
+ @Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId,
+ boolean onlyRevokeCurrentTarget) {
final String key = (newRecord != null) ? newRecord.getKey() : oldRecord.getKey();
if (DBG) Slog.d(TAG, key + ": updating permissions");
@@ -7785,7 +7792,9 @@
}
// If we have no Uris to grant, but an existing owner, go destroy it
- if (newUris == null && permissionOwner != null) {
+ // When revoking permissions of a single listener, destroying the owner will revoke
+ // permissions of other listeners who need to keep access.
+ if (newUris == null && permissionOwner != null && !onlyRevokeCurrentTarget) {
destroyPermissionOwner(permissionOwner, UserHandle.getUserId(oldRecord.getUid()), key);
permissionOwner = null;
}
@@ -7808,9 +7817,20 @@
final Uri uri = oldUris.valueAt(i);
if (newUris == null || !newUris.contains(uri)) {
if (DBG) Slog.d(TAG, key + ": revoking " + uri);
- int userId = ContentProvider.getUserIdFromUri(
- uri, UserHandle.getUserId(oldRecord.getUid()));
- revokeUriPermission(permissionOwner, uri, userId);
+ if (onlyRevokeCurrentTarget) {
+ // We're revoking permission from one listener only; other listeners may
+ // still need access because the notification may still exist
+ revokeUriPermission(permissionOwner, uri,
+ UserHandle.getUserId(oldRecord.getUid()), targetPkg, targetUserId);
+ } else {
+ // This is broad to unilaterally revoke permissions to this Uri as granted
+ // by this notification. But this code-path can only be used when the
+ // reason for revoking is that the notification posted again without this
+ // Uri, not when removing an individual listener.
+ revokeUriPermission(permissionOwner, uri,
+ UserHandle.getUserId(oldRecord.getUid()),
+ null, UserHandle.USER_ALL);
+ }
}
}
}
@@ -7839,8 +7859,10 @@
}
}
- private void revokeUriPermission(IBinder owner, Uri uri, int userId) {
+ private void revokeUriPermission(IBinder owner, Uri uri, int sourceUserId, String targetPkg,
+ int targetUserId) {
if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
+ int userId = ContentProvider.getUserIdFromUri(uri, sourceUserId);
final long ident = Binder.clearCallingIdentity();
try {
@@ -7848,7 +7870,7 @@
owner,
ContentProvider.getUriWithoutUserId(uri),
Intent.FLAG_GRANT_READ_URI_PERMISSION,
- userId);
+ userId, targetPkg, targetUserId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -9219,6 +9241,7 @@
final NotificationRankingUpdate update;
synchronized (mNotificationLock) {
update = makeRankingUpdateLocked(info);
+ updateUriPermissionsForActiveNotificationsLocked(info, true);
}
try {
listener.onListenerConnected(update);
@@ -9230,6 +9253,7 @@
@Override
@GuardedBy("mNotificationLock")
protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
+ updateUriPermissionsForActiveNotificationsLocked(removed, false);
if (removeDisabledHints(removed)) {
updateListenerHintsLocked();
updateEffectsSuppressorLocked();
@@ -9296,8 +9320,7 @@
for (final ManagedServiceInfo info : getServices()) {
boolean sbnVisible = isVisibleToListener(sbn, info);
- boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info)
- : false;
+ boolean oldSbnVisible = (oldSbn != null) && isVisibleToListener(oldSbn, info);
// This notification hasn't been and still isn't visible -> ignore.
if (!oldSbnVisible && !sbnVisible) {
continue;
@@ -9321,13 +9344,8 @@
// This notification became invisible -> remove the old one.
if (oldSbnVisible && !sbnVisible) {
final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- notifyRemoved(
- info, oldSbnLightClone, update, null, REASON_USER_STOPPED);
- }
- });
+ mHandler.post(() -> notifyRemoved(
+ info, oldSbnLightClone, update, null, REASON_USER_STOPPED));
continue;
}
@@ -9337,12 +9355,7 @@
updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);
final StatusBarNotification sbnToPost = trimCache.ForListener(info);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- notifyPosted(info, sbnToPost, update);
- }
- });
+ mHandler.post(() -> notifyPosted(info, sbnToPost, update));
}
} catch (Exception e) {
Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);
@@ -9350,6 +9363,46 @@
}
/**
+ * Synchronously grant or revoke permissions to Uris for all active and visible
+ * notifications to just the NotificationListenerService provided.
+ */
+ @GuardedBy("mNotificationLock")
+ private void updateUriPermissionsForActiveNotificationsLocked(
+ ManagedServiceInfo info, boolean grant) {
+ try {
+ for (final NotificationRecord r : mNotificationList) {
+ // When granting permissions, ignore notifications which are invisible.
+ // When revoking permissions, all notifications are invisible, so process all.
+ if (grant && !isVisibleToListener(r.getSbn(), info)) {
+ continue;
+ }
+ // If the notification is hidden, permissions are not required by the listener.
+ if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
+ continue;
+ }
+ // Grant or revoke access synchronously
+ final int targetUserId = (info.userid == UserHandle.USER_ALL)
+ ? UserHandle.USER_SYSTEM : info.userid;
+ if (grant) {
+ // Grant permissions by passing arguments as if the notification is new.
+ updateUriPermissions(/* newRecord */ r, /* oldRecord */ null,
+ info.component.getPackageName(), targetUserId);
+ } else {
+ // Revoke permissions by passing arguments as if the notification was
+ // removed, but set `onlyRevokeCurrentTarget` to avoid revoking permissions
+ // granted to *other* targets by this notification's URIs.
+ updateUriPermissions(/* newRecord */ null, /* oldRecord */ r,
+ info.component.getPackageName(), targetUserId,
+ /* onlyRevokeCurrentTarget */ true);
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not " + (grant ? "grant" : "revoke") + " Uri permissions to "
+ + info.component, e);
+ }
+ }
+
+ /**
* asynchronously notify all listeners about a removed notification
*/
@GuardedBy("mNotificationLock")
@@ -9384,18 +9437,11 @@
final NotificationStats stats = mAssistants.isServiceTokenValidLocked(info.service)
? notificationStats : null;
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- notifyRemoved(info, sbnLight, update, stats, reason);
- }
- });
+ mHandler.post(() -> notifyRemoved(info, sbnLight, update, stats, reason));
}
// Revoke access after all listeners have been updated
- mHandler.post(() -> {
- updateUriPermissions(null, r, null, UserHandle.USER_SYSTEM);
- });
+ mHandler.post(() -> updateUriPermissions(null, r, null, UserHandle.USER_SYSTEM));
}
/**
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
index cdb6199..5772dea 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
@@ -75,10 +75,31 @@
void removeUriPermissionsForPackage(
String packageName, int userHandle, boolean persistable, boolean targetOnly);
/**
- * @param uri This uri must NOT contain an embedded userId.
+ * Remove any {@link UriPermission} associated with the owner whose values match the given
+ * filtering parameters.
+ *
+ * @param token An opaque owner token as returned by {@link #newUriPermissionOwner(String)}.
+ * @param uri This uri must NOT contain an embedded userId. {@code null} to apply to all Uris.
+ * @param mode The modes (as a bitmask) to revoke.
* @param userId The userId in which the uri is to be resolved.
*/
void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId);
+
+ /**
+ * Remove any {@link UriPermission} associated with the owner whose values match the given
+ * filtering parameters.
+ *
+ * @param token An opaque owner token as returned by {@link #newUriPermissionOwner(String)}.
+ * @param uri This uri must NOT contain an embedded userId. {@code null} to apply to all Uris.
+ * @param mode The modes (as a bitmask) to revoke.
+ * @param userId The userId in which the uri is to be resolved.
+ * @param targetPkg Calling package name to match, or {@code null} to apply to all packages.
+ * @param targetUserId Calling user to match, or {@link UserHandle#USER_ALL} to apply to all
+ * users.
+ */
+ void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId,
+ String targetPkg, int targetUserId);
+
boolean checkAuthorityGrants(
int callingUid, ProviderInfo cpi, int userId, boolean checkUser);
void dump(PrintWriter pw, boolean dumpAll, String dumpPackage);
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index f5e1602..a106dc6 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -51,7 +51,6 @@
import android.app.GrantedUriPermission;
import android.app.IUriGrantsManager;
import android.content.ClipData;
-import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
@@ -88,11 +87,11 @@
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
-import libcore.io.IoUtils;
-
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
+import libcore.io.IoUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -1431,16 +1430,18 @@
@Override
public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId) {
+ revokeUriPermissionFromOwner(token, uri, mode, userId, null, UserHandle.USER_ALL);
+ }
+
+ @Override
+ public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId,
+ String targetPkg, int targetUserId) {
final UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
if (owner == null) {
throw new IllegalArgumentException("Unknown owner: " + token);
}
-
- if (uri == null) {
- owner.removeUriPermissions(mode);
- } else {
- owner.removeUriPermission(new GrantUri(userId, uri, mode), mode);
- }
+ GrantUri grantUri = uri == null ? null : new GrantUri(userId, uri, mode);
+ owner.removeUriPermission(grantUri, mode, targetPkg, targetUserId);
}
@Override
diff --git a/services/core/java/com/android/server/uri/UriPermissionOwner.java b/services/core/java/com/android/server/uri/UriPermissionOwner.java
index 2b404a4..0c26399 100644
--- a/services/core/java/com/android/server/uri/UriPermissionOwner.java
+++ b/services/core/java/com/android/server/uri/UriPermissionOwner.java
@@ -21,6 +21,7 @@
import android.os.Binder;
import android.os.IBinder;
+import android.os.UserHandle;
import android.util.ArraySet;
import android.util.proto.ProtoOutputStream;
@@ -74,30 +75,47 @@
}
void removeUriPermission(GrantUri grantUri, int mode) {
+ removeUriPermission(grantUri, mode, null, UserHandle.USER_ALL);
+ }
+
+ void removeUriPermission(GrantUri grantUri, int mode, String targetPgk, int targetUserId) {
if ((mode & FLAG_GRANT_READ_URI_PERMISSION) != 0 && mReadPerms != null) {
Iterator<UriPermission> it = mReadPerms.iterator();
while (it.hasNext()) {
UriPermission perm = it.next();
- if (grantUri == null || grantUri.equals(perm.uri)) {
- perm.removeReadOwner(this);
- mService.removeUriPermissionIfNeeded(perm);
- it.remove();
+ if (grantUri != null && !grantUri.equals(perm.uri)) {
+ continue;
}
+ if (targetPgk != null && !targetPgk.equals(perm.targetPkg)) {
+ continue;
+ }
+ if (targetUserId != UserHandle.USER_ALL && targetUserId != perm.targetUserId) {
+ continue;
+ }
+ perm.removeReadOwner(this);
+ mService.removeUriPermissionIfNeeded(perm);
+ it.remove();
}
if (mReadPerms.isEmpty()) {
mReadPerms = null;
}
}
- if ((mode & FLAG_GRANT_WRITE_URI_PERMISSION) != 0
- && mWritePerms != null) {
+ if ((mode & FLAG_GRANT_WRITE_URI_PERMISSION) != 0 && mWritePerms != null) {
Iterator<UriPermission> it = mWritePerms.iterator();
while (it.hasNext()) {
UriPermission perm = it.next();
- if (grantUri == null || grantUri.equals(perm.uri)) {
- perm.removeWriteOwner(this);
- mService.removeUriPermissionIfNeeded(perm);
- it.remove();
+ if (grantUri != null && !grantUri.equals(perm.uri)) {
+ continue;
}
+ if (targetPgk != null && !targetPgk.equals(perm.targetPkg)) {
+ continue;
+ }
+ if (targetUserId != UserHandle.USER_ALL && targetUserId != perm.targetUserId) {
+ continue;
+ }
+ perm.removeWriteOwner(this);
+ mService.removeUriPermissionIfNeeded(perm);
+ it.remove();
}
if (mWritePerms.isEmpty()) {
mWritePerms = null;
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 6a797f3..03dce4c 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -32,7 +32,6 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.server.am.ActivityManagerInternalTest.CustomThread;
-import static com.android.server.am.ActivityManagerService.DISPATCH_UIDS_CHANGED_UI_MSG;
import static com.android.server.am.ActivityManagerService.Injector;
import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK;
import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE;
@@ -548,10 +547,10 @@
pendingChange.processState = procStatesForPendingUidRecords[i];
pendingChange.procStateSeq = i;
changeItems.put(changesForPendingUidRecords[i], pendingChange);
- mAms.mPendingUidChanges.add(pendingChange);
+ mAms.mUidObserverController.mPendingUidChanges.add(pendingChange);
}
- mAms.dispatchUidsChanged();
+ mAms.mUidObserverController.dispatchUidsChanged();
// Verify the required changes have been dispatched to observers.
for (int i = 0; i < observers.length; ++i) {
final int changeToObserve = changesToObserve[i];
@@ -647,8 +646,8 @@
changeItem.change = UidRecord.CHANGE_PROCSTATE;
changeItem.processState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
changeItem.procStateSeq = 111;
- mAms.mPendingUidChanges.add(changeItem);
- mAms.dispatchUidsChanged();
+ mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+ mAms.mUidObserverController.dispatchUidsChanged();
// First process state message is always delivered regardless of whether the process state
// change is above or below the cutpoint (PROCESS_STATE_SERVICE).
verify(observer).onUidStateChanged(TEST_UID,
@@ -657,15 +656,15 @@
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_RECEIVER;
- mAms.mPendingUidChanges.add(changeItem);
- mAms.dispatchUidsChanged();
+ mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+ mAms.mUidObserverController.dispatchUidsChanged();
// Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is also below cutpoint, so no callback will be invoked.
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
- mAms.mPendingUidChanges.add(changeItem);
- mAms.dispatchUidsChanged();
+ mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+ mAms.mUidObserverController.dispatchUidsChanged();
// Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is above cutpoint, so callback will be invoked with the
// current process state change.
@@ -675,15 +674,15 @@
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_TOP;
- mAms.mPendingUidChanges.add(changeItem);
- mAms.dispatchUidsChanged();
+ mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+ mAms.mUidObserverController.dispatchUidsChanged();
// Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is also above cutpoint, so no callback will be invoked.
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
- mAms.mPendingUidChanges.add(changeItem);
- mAms.dispatchUidsChanged();
+ mAms.mUidObserverController.mPendingUidChanges.add(changeItem);
+ mAms.mUidObserverController.dispatchUidsChanged();
// Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is below cutpoint, so callback will be invoked with the
// current process state change.
@@ -720,20 +719,21 @@
// Verify that when there no observers listening to uid state changes, then there will
// be no changes to validateUids.
- mAms.mPendingUidChanges.addAll(pendingItemsForUids);
- mAms.dispatchUidsChanged();
+ mAms.mUidObserverController.mPendingUidChanges.addAll(pendingItemsForUids);
+ mAms.mUidObserverController.dispatchUidsChanged();
assertEquals("No observers registered, so validateUids should be empty",
- 0, mAms.mValidateUids.size());
+ 0, mAms.mUidObserverController.mValidateUids.size());
final IUidObserver observer = mock(IUidObserver.Stub.class);
when(observer.asBinder()).thenReturn((IBinder) observer);
mAms.registerUidObserver(observer, 0, 0, null);
// Verify that when observers are registered, then validateUids is correctly updated.
- mAms.mPendingUidChanges.addAll(pendingItemsForUids);
- mAms.dispatchUidsChanged();
+ mAms.mUidObserverController.mPendingUidChanges.addAll(pendingItemsForUids);
+ mAms.mUidObserverController.dispatchUidsChanged();
for (int i = 0; i < pendingItemsForUids.size(); ++i) {
final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
- final UidRecord validateUidRecord = mAms.mValidateUids.get(item.uid);
+ final UidRecord validateUidRecord =
+ mAms.mUidObserverController.mValidateUids.get(item.uid);
if ((item.change & UidRecord.CHANGE_GONE) != 0) {
assertNull("validateUidRecord should be null since the change is either "
+ "CHANGE_GONE or CHANGE_GONE_IDLE", validateUidRecord);
@@ -759,7 +759,7 @@
// Verify that when uid state changes to CHANGE_GONE or CHANGE_GONE_IDLE, then it
// will be removed from validateUids.
assertNotEquals("validateUids should not be empty", 0,
- mAms.mValidateUids.size());
+ mAms.mUidObserverController.mValidateUids.size());
for (int i = 0; i < pendingItemsForUids.size(); ++i) {
final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
// Assign CHANGE_GONE_IDLE to some items and CHANGE_GONE to the others, using even/odd
@@ -767,10 +767,11 @@
item.change = (i % 2) == 0 ? (UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE)
: UidRecord.CHANGE_GONE;
}
- mAms.mPendingUidChanges.addAll(pendingItemsForUids);
- mAms.dispatchUidsChanged();
- assertEquals("validateUids should be empty, size=" + mAms.mValidateUids.size(),
- 0, mAms.mValidateUids.size());
+ mAms.mUidObserverController.mPendingUidChanges.addAll(pendingItemsForUids);
+ mAms.mUidObserverController.dispatchUidsChanged();
+ assertEquals("validateUids should be empty, size="
+ + mAms.mUidObserverController.mValidateUids.size(),
+ 0, mAms.mUidObserverController.mValidateUids.size());
}
@Test
@@ -792,7 +793,7 @@
@Test
public void testEnqueueUidChangeLocked_nullUidRecord() {
// Use "null" uidRecord to make sure there is no crash.
- mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
+ mAms.mUidObserverController.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
}
private void verifyLastProcStateSeqUpdated(UidRecord uidRecord, int uid, long curProcstateSeq) {
@@ -801,7 +802,7 @@
final int changeToDispatch = UID_RECORD_CHANGES[i];
// Reset lastProcStateSeqDispatchToObservers after every test.
uidRecord.lastDispatchedProcStateSeq = 0;
- mAms.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch);
+ mAms.mUidObserverController.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch);
// Verify there is no effect on curProcStateSeq.
assertEquals(curProcstateSeq, uidRecord.curProcStateSeq);
if ((changeToDispatch & UidRecord.CHANGE_GONE) != 0) {
@@ -833,9 +834,9 @@
// Reset the current state
mHandler.reset();
uidRecord.pendingChange = null;
- mAms.mPendingUidChanges.clear();
+ mAms.mUidObserverController.mPendingUidChanges.clear();
- mAms.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch);
+ mAms.mUidObserverController.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch);
// Verify that UidRecord.pendingChange is updated correctly.
assertNotNull(uidRecord.pendingChange);
@@ -843,8 +844,7 @@
assertEquals(expectedProcState, uidRecord.pendingChange.processState);
assertEquals(TEST_PROC_STATE_SEQ1, uidRecord.pendingChange.procStateSeq);
- // Verify that DISPATCH_UIDS_CHANGED_UI_MSG is posted to handler.
- mHandler.waitForMessage(DISPATCH_UIDS_CHANGED_UI_MSG);
+ // TODO: Verify that DISPATCH_UIDS_CHANGED_UI_MSG is posted to handler.
}
}
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 5b2d738..9319bea 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3886,7 +3886,7 @@
mService.updateUriPermissions(recordB, recordA, mContext.getPackageName(),
USER_SYSTEM);
verify(mUgmInternal, times(1)).revokeUriPermissionFromOwner(any(),
- eq(message1.getDataUri()), anyInt(), anyInt());
+ eq(message1.getDataUri()), anyInt(), anyInt(), eq(null), eq(-1));
// Update back means we grant access to first again
reset(mUgm);
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3646647..6288bc1 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -2488,6 +2488,42 @@
}
/**
+ * Ask some other {@code ConnectionService} to create a {@code RemoteConference} given an
+ * incoming request. This is used by {@code ConnectionService}s that are registered with
+ * {@link PhoneAccount#CAPABILITY_ADHOC_CONFERENCE_CALLING}.
+ *
+ * @param connectionManagerPhoneAccount See description at
+ * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+ * @param request Details about the incoming conference call.
+ * @return The {@code RemoteConference} object to satisfy this call, or {@code null} to not
+ * handle the call.
+ */
+ public final @Nullable RemoteConference createRemoteIncomingConference(
+ @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
+ @Nullable ConnectionRequest request) {
+ return mRemoteConnectionManager.createRemoteConference(connectionManagerPhoneAccount,
+ request, true);
+ }
+
+ /**
+ * Ask some other {@code ConnectionService} to create a {@code RemoteConference} given an
+ * outgoing request. This is used by {@code ConnectionService}s that are registered with
+ * {@link PhoneAccount#CAPABILITY_ADHOC_CONFERENCE_CALLING}.
+ *
+ * @param connectionManagerPhoneAccount See description at
+ * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+ * @param request Details about the outgoing conference call.
+ * @return The {@code RemoteConference} object to satisfy this call, or {@code null} to not
+ * handle the call.
+ */
+ public final @Nullable RemoteConference createRemoteOutgoingConference(
+ @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
+ @Nullable ConnectionRequest request) {
+ return mRemoteConnectionManager.createRemoteConference(connectionManagerPhoneAccount,
+ request, false);
+ }
+
+ /**
* Indicates to the relevant {@code RemoteConnectionService} that the specified
* {@link RemoteConnection}s should be merged into a conference call.
* <p>
diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java
index 502b7c0..e024e61 100644
--- a/telecomm/java/android/telecom/RemoteConference.java
+++ b/telecomm/java/android/telecom/RemoteConference.java
@@ -16,14 +16,14 @@
package android.telecom;
-import com.android.internal.telecom.IConnectionService;
-
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import com.android.internal.telecom.IConnectionService;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -155,6 +155,14 @@
}
/** @hide */
+ RemoteConference(DisconnectCause disconnectCause) {
+ mId = "NULL";
+ mConnectionService = null;
+ mState = Connection.STATE_DISCONNECTED;
+ mDisconnectCause = disconnectCause;
+ }
+
+ /** @hide */
String getId() {
return mId;
}
@@ -583,4 +591,16 @@
}
}
}
+
+ /**
+ * Create a {@link RemoteConference} represents a failure, and which will
+ * be in {@link Connection#STATE_DISCONNECTED}.
+ *
+ * @param disconnectCause The disconnect cause.
+ * @return a failed {@link RemoteConference}
+ * @hide
+ */
+ public static RemoteConference failure(DisconnectCause disconnectCause) {
+ return new RemoteConference(disconnectCause);
+ }
}
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index df336257..52210a5 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -16,10 +16,6 @@
package android.telecom;
-import com.android.internal.telecom.IConnectionService;
-import com.android.internal.telecom.IVideoCallback;
-import com.android.internal.telecom.IVideoProvider;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -33,6 +29,10 @@
import android.telecom.Logging.Session;
import android.view.Surface;
+import com.android.internal.telecom.IConnectionService;
+import com.android.internal.telecom.IVideoCallback;
+import com.android.internal.telecom.IVideoProvider;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -1114,6 +1114,23 @@
}
/**
+ * Instructs this {@link RemoteConnection} to initiate a conference with a list of
+ * participants.
+ * <p>
+ *
+ * @param participants with which conference call will be formed.
+ */
+ public void addConferenceParticipants(@NonNull List<Uri> participants) {
+ try {
+ if (mConnected) {
+ mConnectionService.addConferenceParticipants(mConnectionId, participants,
+ null /*Session.Info*/);
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
* Set the audio state of this {@code RemoteConnection}.
*
* @param state The audio state of this {@code RemoteConnection}.
diff --git a/telecomm/java/android/telecom/RemoteConnectionManager.java b/telecomm/java/android/telecom/RemoteConnectionManager.java
index 0322218..f3c7bd8 100644
--- a/telecomm/java/android/telecom/RemoteConnectionManager.java
+++ b/telecomm/java/android/telecom/RemoteConnectionManager.java
@@ -73,6 +73,37 @@
return null;
}
+ /**
+ * Ask a {@code RemoteConnectionService} to create a {@code RemoteConference}.
+ * @param connectionManagerPhoneAccount See description at
+ * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+ * @param request Details about the incoming conference call.
+ * @param isIncoming {@code true} if it's an incoming conference.
+ * @return
+ */
+ public RemoteConference createRemoteConference(
+ PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request,
+ boolean isIncoming) {
+ PhoneAccountHandle accountHandle = request.getAccountHandle();
+ if (accountHandle == null) {
+ throw new IllegalArgumentException("accountHandle must be specified.");
+ }
+
+ ComponentName componentName = request.getAccountHandle().getComponentName();
+ if (!mRemoteConnectionServices.containsKey(componentName)) {
+ throw new UnsupportedOperationException("accountHandle not supported: "
+ + componentName);
+ }
+
+ RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName);
+ if (remoteService != null) {
+ return remoteService.createRemoteConference(
+ connectionManagerPhoneAccount, request, isIncoming);
+ }
+ return null;
+ }
+
public void conferenceRemoteConnections(RemoteConnection a, RemoteConnection b) {
if (a.getConnectionService() == b.getConnectionService()) {
try {
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index a083301..bf6a6ef7 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -31,9 +31,9 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.List;
import java.util.UUID;
/**
@@ -591,6 +591,38 @@
}
}
+ RemoteConference createRemoteConference(
+ PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request,
+ boolean isIncoming) {
+ final String id = UUID.randomUUID().toString();
+ try {
+ if (mConferenceById.isEmpty()) {
+ mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(),
+ null /*Session.Info*/);
+ }
+ RemoteConference conference = new RemoteConference(id, mOutgoingConnectionServiceRpc);
+ mOutgoingConnectionServiceRpc.createConference(connectionManagerPhoneAccount,
+ id,
+ request,
+ isIncoming,
+ false /* isUnknownCall */,
+ null /*Session.info*/);
+ conference.registerCallback(new RemoteConference.Callback() {
+ @Override
+ public void onDestroyed(RemoteConference conference) {
+ mConferenceById.remove(id);
+ maybeDisconnectAdapter();
+ }
+ });
+ conference.putExtras(request.getExtras());
+ return conference;
+ } catch (RemoteException e) {
+ return RemoteConference.failure(
+ new DisconnectCause(DisconnectCause.ERROR, e.toString()));
+ }
+ }
+
private boolean hasConnection(String callId) {
return mConnectionById.containsKey(callId);
}