Merge "[Ongoing Call] Use the notification's key to identify when to remove the ongoing call chip, rather than the notification's template." into sc-dev
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 7dccf01..ef7fac3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -53,8 +53,8 @@
private val logger: OngoingCallLogger
) : CallbackController<OngoingCallListener> {
- /** Null if there's no ongoing call. */
- private var ongoingCallInfo: OngoingCallInfo? = null
+ /** Non-null if there's an active call notification. */
+ private var callNotificationInfo: CallNotificationInfo? = null
/** True if the application managing the call is visible to the user. */
private var isCallAppVisible: Boolean = true
private var chipView: View? = null
@@ -76,19 +76,34 @@
}
override fun onEntryUpdated(entry: NotificationEntry) {
- if (isOngoingCallNotification(entry)) {
- ongoingCallInfo = OngoingCallInfo(
- entry.sbn.notification.`when`,
- entry.sbn.notification.contentIntent.intent,
- entry.sbn.uid)
- updateChip()
- } else if (isCallNotification(entry)) {
- removeChip()
+ // We have a new call notification or our existing call notification has been updated.
+ // TODO(b/183229367): This likely won't work if you take a call from one app then
+ // switch to a call from another app.
+ if (callNotificationInfo == null && isCallNotification(entry) ||
+ (entry.sbn.key == callNotificationInfo?.key)) {
+ val newOngoingCallInfo = CallNotificationInfo(
+ entry.sbn.key,
+ entry.sbn.notification.`when`,
+ entry.sbn.notification.contentIntent.intent,
+ entry.sbn.uid,
+ entry.sbn.notification.extras.getInt(
+ Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING
+ )
+ if (newOngoingCallInfo == callNotificationInfo) {
+ return
+ }
+
+ callNotificationInfo = newOngoingCallInfo
+ if (newOngoingCallInfo.isOngoing) {
+ updateChip()
+ } else {
+ removeChip()
+ }
}
}
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
- if (isOngoingCallNotification(entry)) {
+ if (entry.sbn.key == callNotificationInfo?.key) {
removeChip()
}
}
@@ -126,7 +141,7 @@
* Returns true if there's an active ongoing call that should be displayed in a status bar chip.
*/
fun hasOngoingCall(): Boolean {
- return ongoingCallInfo != null &&
+ return callNotificationInfo?.isOngoing == true &&
// When the user is in the phone app, don't show the chip.
!isCallAppVisible
}
@@ -146,7 +161,7 @@
}
private fun updateChip() {
- val currentOngoingCallInfo = ongoingCallInfo ?: return
+ val currentCallNotificationInfo = callNotificationInfo ?: return
val currentChipView = chipView
val timeView =
@@ -155,7 +170,7 @@
currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
if (currentChipView != null && timeView != null && backgroundView != null) {
- timeView.base = currentOngoingCallInfo.callStartTime -
+ timeView.base = currentCallNotificationInfo.callStartTime -
System.currentTimeMillis() +
systemClock.elapsedRealtime()
timeView.start()
@@ -163,17 +178,17 @@
currentChipView.setOnClickListener {
logger.logChipClicked()
activityStarter.postStartActivityDismissingKeyguard(
- currentOngoingCallInfo.intent, 0,
+ currentCallNotificationInfo.intent, 0,
ActivityLaunchAnimator.Controller.fromView(backgroundView))
}
- setUpUidObserver(currentOngoingCallInfo)
+ setUpUidObserver(currentCallNotificationInfo)
mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
} else {
- // If we failed to update the chip, don't store the ongoing call info. Then
- // [hasOngoingCall] will return false and we fall back to typical notification handling.
- ongoingCallInfo = null
+ // If we failed to update the chip, don't store the call info. Then [hasOngoingCall]
+ // will return false and we fall back to typical notification handling.
+ callNotificationInfo = null
if (DEBUG) {
Log.w(TAG, "Ongoing call chip view could not be found; " +
@@ -185,14 +200,14 @@
/**
* Sets up an [IUidObserver] to monitor the status of the application managing the ongoing call.
*/
- private fun setUpUidObserver(currentOngoingCallInfo: OngoingCallInfo) {
+ private fun setUpUidObserver(currentCallNotificationInfo: CallNotificationInfo) {
isCallAppVisible = isProcessVisibleToUser(
- iActivityManager.getUidProcessState(currentOngoingCallInfo.uid, null))
+ iActivityManager.getUidProcessState(currentCallNotificationInfo.uid, null))
uidObserver = object : IUidObserver.Stub() {
override fun onUidStateChanged(
uid: Int, procState: Int, procStateSeq: Long, capability: Int) {
- if (uid == currentOngoingCallInfo.uid) {
+ if (uid == currentCallNotificationInfo.uid) {
val oldIsCallAppVisible = isCallAppVisible
isCallAppVisible = isProcessVisibleToUser(procState)
if (oldIsCallAppVisible != isCallAppVisible) {
@@ -225,26 +240,23 @@
}
private fun removeChip() {
- ongoingCallInfo = null
+ callNotificationInfo = null
mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
if (uidObserver != null) {
iActivityManager.unregisterUidObserver(uidObserver)
}
}
- private class OngoingCallInfo(
+ private data class CallNotificationInfo(
+ val key: String,
val callStartTime: Long,
val intent: Intent,
- val uid: Int
+ val uid: Int,
+ /** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
+ val isOngoing: Boolean
)
}
-private fun isOngoingCallNotification(entry: NotificationEntry): Boolean {
- val extras = entry.sbn.notification.extras
- return isCallNotification(entry) &&
- extras.getInt(Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING
-}
-
private fun isCallNotification(entry: NotificationEntry): Boolean {
return entry.sbn.notification.isStyle(Notification.CallStyle::class.java)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 3a71ecf..ca3cff9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -55,6 +55,8 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.reset
+
import org.mockito.MockitoAnnotations
private const val CALL_UID = 900
@@ -138,15 +140,51 @@
.onOngoingCallStateChanged(anyBoolean())
}
+ /**
+ * If a call notification is never added before #onEntryRemoved is called, then the listener
+ * should never be notified.
+ */
@Test
- fun onEntryRemoved_ongoingCallNotif_listenerNotified() {
+ fun onEntryRemoved_callNotifNeverAddedBeforehand_listenerNotNotified() {
notifCollectionListener.onEntryRemoved(createOngoingCallNotifEntry(), REASON_USER_STOPPED)
+ verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
+ }
+
+ @Test
+ fun onEntryRemoved_callNotifAddedThenRemoved_listenerNotified() {
+ val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+ notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+ reset(mockOngoingCallListener)
+
+ notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
+
verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
+ /** Regression test for b/188491504. */
@Test
- fun onEntryRemoved_notOngoingCallNotif_listenerNotNotified() {
+ fun onEntryRemoved_removedNotifHasSameKeyAsAddedNotif_listenerNotified() {
+ val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+ notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+ reset(mockOngoingCallListener)
+
+ // Create another notification based on the ongoing call one, but remove the features that
+ // made it a call notification.
+ val removedEntryBuilder = NotificationEntryBuilder(ongoingCallNotifEntry)
+ removedEntryBuilder.modifyNotification(context).style = null
+
+ notifCollectionListener.onEntryRemoved(removedEntryBuilder.build(), REASON_USER_STOPPED)
+
+ verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
+ }
+
+
+ @Test
+ fun onEntryRemoved_notifKeyDoesNotMatchOngoingCallNotif_listenerNotNotified() {
+ notifCollectionListener.onEntryAdded(createOngoingCallNotifEntry())
+ reset(mockOngoingCallListener)
+
notifCollectionListener.onEntryRemoved(createNotCallNotifEntry(), REASON_USER_STOPPED)
verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
@@ -158,6 +196,20 @@
}
@Test
+ fun hasOngoingCall_unrelatedNotifSent_returnsFalse() {
+ notifCollectionListener.onEntryUpdated(createNotCallNotifEntry())
+
+ assertThat(controller.hasOngoingCall()).isFalse()
+ }
+
+ @Test
+ fun hasOngoingCall_screeningCallNotifSent_returnsFalse() {
+ notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry())
+
+ assertThat(controller.hasOngoingCall()).isFalse()
+ }
+
+ @Test
fun hasOngoingCall_ongoingCallNotifSentAndCallAppNotVisible_returnsTrue() {
`when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
.thenReturn(PROC_STATE_INVISIBLE)
@@ -224,6 +276,7 @@
fun setChipView_whenHasOngoingCallIsTrue_listenerNotifiedWithNewView() {
// Start an ongoing call.
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ reset(mockOngoingCallListener)
lateinit var newChipView: View
TestableLooper.get(this).runWithLooper {
@@ -233,10 +286,7 @@
// Change the chip view associated with the controller.
controller.setChipView(newChipView)
- // Verify the listener was notified once for the initial call and again when the new view
- // was set.
- verify(mockOngoingCallListener, times(2))
- .onOngoingCallStateChanged(anyBoolean())
+ verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
@Test
@@ -245,6 +295,7 @@
`when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
.thenReturn(PROC_STATE_INVISIBLE)
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ reset(mockOngoingCallListener)
val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java)
verify(mockIActivityManager).registerUidObserver(
@@ -256,9 +307,7 @@
mainExecutor.advanceClockToLast()
mainExecutor.runAllReady();
- // Once for when the call was started, and another time when the process visibility changes.
- verify(mockOngoingCallListener, times(2))
- .onOngoingCallStateChanged(anyBoolean())
+ verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
@Test
@@ -267,6 +316,7 @@
`when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
.thenReturn(PROC_STATE_VISIBLE)
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ reset(mockOngoingCallListener)
val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java)
verify(mockIActivityManager).registerUidObserver(
@@ -278,9 +328,7 @@
mainExecutor.advanceClockToLast()
mainExecutor.runAllReady();
- // Once for when the call was started, and another time when the process visibility changes.
- verify(mockOngoingCallListener, times(2))
- .onOngoingCallStateChanged(anyBoolean())
+ verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
@Test