diff options
34 files changed, 1678 insertions, 786 deletions
diff --git a/api/current.txt b/api/current.txt index 8cb5b0d68a19..532c3d938334 100644 --- a/api/current.txt +++ b/api/current.txt @@ -45976,7 +45976,9 @@ package android.telecom { 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 @@ package android.telecom { 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 0ba7f60c47c3..f9d0d14c7335 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -44126,7 +44126,9 @@ package android.telecom { 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 @@ package android.telecom { 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 ee2bb6b1d190..3e110671d174 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 @@ public class PreparationCoordinator implements Coordinator { 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 8849824380d3..f90ec0b4f42a 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 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { .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 9782c3ef55fe..1c02c62602cf 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.ShadeListBuilder; 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 @@ public class NotifPipelineInitializer implements Dumpable { 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 @@ public class NotifPipelineInitializer implements Dumpable { NotifCoordinators notifCoordinators, NotifInflaterImpl notifInflater, DumpManager dumpManager, - NotifViewManagerBuilder notifViewManagerBuilder, + ShadeViewManagerFactory shadeViewManagerFactory, FeatureFlags featureFlags) { mPipelineWrapper = pipelineWrapper; mGroupCoalescer = groupCoalescer; @@ -73,8 +72,8 @@ public class NotifPipelineInitializer implements Dumpable { mNotifPluggableCoordinators = notifCoordinators; mDumpManager = dumpManager; mNotifInflater = notifInflater; + mShadeViewManagerFactory = shadeViewManagerFactory; mFeatureFlags = featureFlags; - mNotifViewManagerBuilder = notifViewManagerBuilder; } /** Hooks the new pipeline up to NotificationManager */ @@ -95,8 +94,7 @@ public class NotifPipelineInitializer implements Dumpable { // 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 000000000000..67f7b1c09df3 --- /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 54000950e3ef..00fd09dd662e 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 @@ package com.android.systemui.statusbar.notification.collection.render 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 @@ class NotifViewBarn @Inject constructor() { 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 f2e2c39ab612..000000000000 --- 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 000000000000..e8124944bcb0 --- /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 000000000000..019520f18982 --- /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 index 3d561264d367..19e156f572d4 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.LogLevel 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 000000000000..201be59d80b4 --- /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 86a3271c4eac..1dbec6603f16 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 static com.android.systemui.statusbar.NotificationRemoteInputManager.ENAB 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 @@ import javax.inject.Named; * 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 @@ public class ExpandableNotificationRowController { @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 @@ public class ExpandableNotificationRowController { OnDismissCallback onDismissCallback, FalsingManager falsingManager, PeopleNotificationIdentifier peopleNotificationIdentifier) { mView = view; + mListContainer = listContainer; mActivatableNotificationViewController = activatableNotificationViewController; mMediaManager = mediaManager; mPluginManager = pluginManager; @@ -162,4 +171,52 @@ public class ExpandableNotificationRowController { 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 28ddf5971bcb..becc9a772b28 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.collection.NotificationEntry; 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 @@ public interface ExpandableNotificationRowComponent { 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 000000000000..92c73a412577 --- /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 000000000000..bbe92f67ca2e --- /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 a90af87064b5..7a0a19bd5424 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 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { .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 0038dc2e8da0..b78b5d945a08 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/ServiceWatcher.java @@ -238,24 +238,13 @@ public class ServiceWatcher implements ServiceConnection { 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); + public boolean onPackageChanged(String packageName, int uid, String[] components) { + return true; } @Override - public boolean onPackageChanged(String packageName, int uid, String[] components) { - ServiceWatcher.this.onPackageChanged(packageName); - return super.onPackageChanged(packageName, uid, components); + public void onSomePackagesChanged() { + onBestServiceChanged(false); } }.register(mContext, UserHandle.ALL, true, mHandler); @@ -320,7 +309,7 @@ public class ServiceWatcher implements ServiceConnection { 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 @@ public class ServiceWatcher implements ServiceConnection { 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 @@ public class ServiceWatcher implements ServiceConnection { 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 @@ public class ServiceWatcher implements ServiceConnection { } 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 class ServiceWatcher implements ServiceConnection { 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 @@ public class ServiceWatcher implements ServiceConnection { } } - 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 601958eec8eb..3e600b793385 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_PROCESSES; 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; 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.SystemServiceManager; 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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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 @@ public class ActivityManagerService extends IActivityManager.Stub @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 @@ public class ActivityManagerService extends IActivityManager.Stub }; /** - * 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub "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 @@ public class ActivityManagerService extends IActivityManager.Stub } 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } - 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 @@ public class ActivityManagerService extends IActivityManager.Stub @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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 bf15f1737cfc..bad042cd3a68 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 @@ public final class OomAdjuster { 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 5721fb7f5128..2e8660e1317a 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 @@ public final class ProcessList { // 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 000000000000..4c4dd8b8defa --- /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 c3532a84c42d..05aa3150cfef 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 @@ class LocationProviderManager extends // 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 @@ class LocationProviderManager extends } else { mWorkSource = identity.addToWorkSource(null); } + + mProviderLocationRequest = super.getRequest(); } @GuardedBy("mLock") @@ -313,7 +315,7 @@ class LocationProviderManager extends @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 2d052da22714..52928dc45131 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 @@ public class NotificationManagerService extends SystemService { @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 @@ public class NotificationManagerService extends SystemService { } // 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { } } - 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 @@ public class NotificationManagerService extends SystemService { owner, ContentProvider.getUriWithoutUserId(uri), Intent.FLAG_GRANT_READ_URI_PERMISSION, - userId); + userId, targetPkg, targetUserId); } finally { Binder.restoreCallingIdentity(ident); } @@ -9219,6 +9241,7 @@ public class NotificationManagerService extends SystemService { final NotificationRankingUpdate update; synchronized (mNotificationLock) { update = makeRankingUpdateLocked(info); + updateUriPermissionsForActiveNotificationsLocked(info, true); } try { listener.onListenerConnected(update); @@ -9230,6 +9253,7 @@ public class NotificationManagerService extends SystemService { @Override @GuardedBy("mNotificationLock") protected void onServiceRemovedLocked(ManagedServiceInfo removed) { + updateUriPermissionsForActiveNotificationsLocked(removed, false); if (removeDisabledHints(removed)) { updateListenerHintsLocked(); updateEffectsSuppressorLocked(); @@ -9296,8 +9320,7 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { // 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { } /** + * 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 @@ public class NotificationManagerService extends SystemService { 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 cdb61995c336..5772dea287fc 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 @@ public interface UriGrantsManagerInternal { 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 f5e1602ee6be..a106dc682208 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.AppGlobals; 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.LocalServices; 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 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { @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 2b404a43a338..0c263997a8b5 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 static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION; 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 @@ public class UriPermissionOwner { } 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 6a797f31fa55..03dce4c62fe8 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 android.util.DebugUtils.valueToString; 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 @@ public class ActivityManagerServiceTest { 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 @@ public class ActivityManagerServiceTest { 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 @@ public class ActivityManagerServiceTest { 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 @@ public class ActivityManagerServiceTest { 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 @@ public class ActivityManagerServiceTest { // 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 @@ public class ActivityManagerServiceTest { // 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 @@ public class ActivityManagerServiceTest { 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 @@ public class ActivityManagerServiceTest { @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 @@ public class ActivityManagerServiceTest { 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 @@ public class ActivityManagerServiceTest { // 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 @@ public class ActivityManagerServiceTest { 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 5b2d738eb760..9319bea497fb 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 3646647e9734..6288bc1698e9 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -2488,6 +2488,42 @@ public abstract class ConnectionService extends Service { } /** + * 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 502b7c01b0c0..e024e6186519 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 @@ public final class RemoteConference { } /** @hide */ + RemoteConference(DisconnectCause disconnectCause) { + mId = "NULL"; + mConnectionService = null; + mState = Connection.STATE_DISCONNECTED; + mDisconnectCause = disconnectCause; + } + + /** @hide */ String getId() { return mId; } @@ -583,4 +591,16 @@ public final class RemoteConference { } } } + + /** + * 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 df3362578680..52210a55c8d0 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.os.RemoteException; 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 @@ public final class RemoteConnection { } /** + * 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 0322218d75dc..f3c7bd83ed4b 100644 --- a/telecomm/java/android/telecom/RemoteConnectionManager.java +++ b/telecomm/java/android/telecom/RemoteConnectionManager.java @@ -73,6 +73,37 @@ public class RemoteConnectionManager { 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 a0833011715d..bf6a6ef793ff 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -31,9 +31,9 @@ import com.android.internal.telecom.RemoteServiceCallback; 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 @@ final class RemoteConnectionService { } } + 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); } |