summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallListener.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt105
5 files changed, 181 insertions, 31 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index fad748021559..eb7854e63a85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.dagger;
+import android.app.IActivityManager;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Handler;
@@ -68,6 +69,7 @@ import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.bubbles.Bubbles;
import java.util.Optional;
+import java.util.concurrent.Executor;
import dagger.Binds;
import dagger.Lazy;
@@ -239,10 +241,13 @@ public interface StatusBarDependenciesModule {
CommonNotifCollection notifCollection,
FeatureFlags featureFlags,
SystemClock systemClock,
- ActivityStarter activityStarter) {
+ ActivityStarter activityStarter,
+ @Main Executor mainExecutor,
+ IActivityManager iActivityManager) {
OngoingCallController ongoingCallController =
new OngoingCallController(
- notifCollection, featureFlags, systemClock, activityStarter);
+ notifCollection, featureFlags, systemClock, activityStarter, mainExecutor,
+ iActivityManager);
ongoingCallController.init();
return ongoingCallController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 76657ad5ab07..9986a233e170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -106,12 +106,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private final OngoingCallListener mOngoingCallListener = new OngoingCallListener() {
@Override
- public void onOngoingCallStarted(boolean animate) {
- disable(getContext().getDisplayId(), mDisabled1, mDisabled2, animate);
- }
-
- @Override
- public void onOngoingCallEnded(boolean animate) {
+ public void onOngoingCallStateChanged(boolean animate) {
disable(getContext().getDisplayId(), mDisabled1, mDisabled2, animate);
}
};
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 51bb6434e61c..6d1df5b5fa19 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
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.phone.ongoingcall
+import android.app.ActivityManager
+import android.app.IActivityManager
+import android.app.IUidObserver
import android.app.Notification
import android.app.Notification.CallStyle.CALL_TYPE_ONGOING
import android.content.Intent
@@ -25,6 +28,7 @@ import android.widget.Chronometer
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -32,6 +36,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Co
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.util.time.SystemClock
+import java.util.concurrent.Executor
import javax.inject.Inject
/**
@@ -42,12 +47,17 @@ class OngoingCallController @Inject constructor(
private val notifCollection: CommonNotifCollection,
private val featureFlags: FeatureFlags,
private val systemClock: SystemClock,
- private val activityStarter: ActivityStarter
+ private val activityStarter: ActivityStarter,
+ @Main private val mainExecutor: Executor,
+ private val iActivityManager: IActivityManager
) : CallbackController<OngoingCallListener> {
/** Null if there's no ongoing call. */
private var ongoingCallInfo: OngoingCallInfo? = null
+ /** True if the application managing the call is visible to the user. */
+ private var isCallAppVisible: Boolean = true
private var chipView: ViewGroup? = null
+ private var uidObserver: IUidObserver.Stub? = null
private val mListeners: MutableList<OngoingCallListener> = mutableListOf()
@@ -67,8 +77,9 @@ class OngoingCallController @Inject constructor(
override fun onEntryUpdated(entry: NotificationEntry) {
if (isOngoingCallNotification(entry)) {
ongoingCallInfo = OngoingCallInfo(
- entry.sbn.notification.`when`,
- entry.sbn.notification.contentIntent.intent)
+ entry.sbn.notification.`when`,
+ entry.sbn.notification.contentIntent.intent,
+ entry.sbn.uid)
updateChip()
}
}
@@ -76,7 +87,10 @@ class OngoingCallController @Inject constructor(
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
if (isOngoingCallNotification(entry)) {
ongoingCallInfo = null
- mListeners.forEach { l -> l.onOngoingCallEnded(animate = true) }
+ mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
+ if (uidObserver != null) {
+ iActivityManager.unregisterUidObserver(uidObserver)
+ }
}
}
}
@@ -100,9 +114,13 @@ class OngoingCallController @Inject constructor(
}
/**
- * Returns true if there's an active ongoing call that can be displayed in a status bar chip.
+ * Returns true if there's an active ongoing call that should be displayed in a status bar chip.
*/
- fun hasOngoingCall(): Boolean = ongoingCallInfo != null
+ fun hasOngoingCall(): Boolean {
+ return ongoingCallInfo != null &&
+ // When the user is in the phone app, don't show the chip.
+ !isCallAppVisible
+ }
override fun addCallback(listener: OngoingCallListener) {
synchronized(mListeners) {
@@ -137,7 +155,9 @@ class OngoingCallController @Inject constructor(
ActivityLaunchAnimator.Controller.fromView(it))
}
- mListeners.forEach { l -> l.onOngoingCallStarted(animate = true) }
+ setUpUidObserver(currentOngoingCallInfo)
+
+ 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.
@@ -150,9 +170,52 @@ class OngoingCallController @Inject constructor(
}
}
+ /**
+ * Sets up an [IUidObserver] to monitor the status of the application managing the ongoing call.
+ */
+ private fun setUpUidObserver(currentOngoingCallInfo: OngoingCallInfo) {
+ isCallAppVisible = isProcessVisibleToUser(
+ iActivityManager.getUidProcessState(currentOngoingCallInfo.uid, null))
+
+ uidObserver = object : IUidObserver.Stub() {
+ override fun onUidStateChanged(
+ uid: Int, procState: Int, procStateSeq: Long, capability: Int) {
+ if (uid == currentOngoingCallInfo.uid) {
+ val oldIsCallAppVisible = isCallAppVisible
+ isCallAppVisible = isProcessVisibleToUser(procState)
+ if (oldIsCallAppVisible != isCallAppVisible) {
+ // Animations may be run as a result of the call's state change, so ensure
+ // the listener is notified on the main thread.
+ mainExecutor.execute {
+ mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
+ }
+ }
+ }
+ }
+
+ override fun onUidGone(uid: Int, disabled: Boolean) {}
+ override fun onUidActive(uid: Int) {}
+ override fun onUidIdle(uid: Int, disabled: Boolean) {}
+ override fun onUidCachedChanged(uid: Int, cached: Boolean) {}
+ }
+
+ iActivityManager.registerUidObserver(
+ uidObserver,
+ ActivityManager.UID_OBSERVER_PROCSTATE,
+ ActivityManager.PROCESS_STATE_UNKNOWN,
+ null
+ )
+ }
+
+ /** Returns true if the given [procState] represents a process that's visible to the user. */
+ private fun isProcessVisibleToUser(procState: Int): Boolean {
+ return procState <= ActivityManager.PROCESS_STATE_TOP
+ }
+
private class OngoingCallInfo(
val callStartTime: Long,
- val intent: Intent
+ val intent: Intent,
+ val uid: Int
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallListener.kt
index 7c583a1491e0..7a124308d597 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallListener.kt
@@ -16,11 +16,13 @@
package com.android.systemui.statusbar.phone.ongoingcall
-/** A listener that's notified when an ongoing call is started or ended. */
+/** A listener that's notified when the state of an ongoing call has changed. */
interface OngoingCallListener {
- /** Called when an ongoing call is started. */
- fun onOngoingCallStarted(animate: Boolean)
- /** Called when an ongoing call is ended. */
- fun onOngoingCallEnded(animate: Boolean)
+ /**
+ * Called when the state of an ongoing call has changed in any way that may affect view
+ * visibility (including call starting, call stopping, application managing the call becoming
+ * visible or invisible).
+ */
+ fun onOngoingCallStateChanged(animate: Boolean)
}
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 c244290fdacd..896e33073cc3 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
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.phone.ongoingcall
+import android.app.ActivityManager
+import android.app.IActivityManager
+import android.app.IUidObserver
import android.app.Notification
import android.app.PendingIntent
import android.app.Person
@@ -34,31 +37,46 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.*
import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
import org.mockito.Mockito.never
import org.mockito.Mockito.times
-import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+private const val CALL_UID = 900
+
+// A process state that represents the process being visible to the user.
+private const val PROC_STATE_VISIBLE = ActivityManager.PROCESS_STATE_TOP
+
+// A process state that represents the process being invisible to the user.
+private const val PROC_STATE_INVISIBLE = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class OngoingCallControllerTest : SysuiTestCase() {
+ private val clock = FakeSystemClock()
+ private val mainExecutor = FakeExecutor(clock)
+
private lateinit var controller: OngoingCallController
private lateinit var notifCollectionListener: NotifCollectionListener
@Mock private lateinit var mockOngoingCallListener: OngoingCallListener
@Mock private lateinit var mockActivityStarter: ActivityStarter
+ @Mock private lateinit var mockIActivityManager: IActivityManager
private lateinit var chipView: LinearLayout
@@ -76,7 +94,12 @@ class OngoingCallControllerTest : SysuiTestCase() {
val notificationCollection = mock(CommonNotifCollection::class.java)
controller = OngoingCallController(
- notificationCollection, featureFlags, FakeSystemClock(), mockActivityStarter)
+ notificationCollection,
+ featureFlags,
+ clock,
+ mockActivityStarter,
+ mainExecutor,
+ mockIActivityManager)
controller.init()
controller.addCallback(mockOngoingCallListener)
controller.setChipView(chipView)
@@ -84,34 +107,37 @@ class OngoingCallControllerTest : SysuiTestCase() {
val collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener::class.java)
verify(notificationCollection).addCollectionListener(collectionListenerCaptor.capture())
notifCollectionListener = collectionListenerCaptor.value!!
+
+ `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
+ .thenReturn(PROC_STATE_INVISIBLE)
}
@Test
fun onEntryUpdated_isOngoingCallNotif_listenerNotifiedWithRightCallTime() {
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- verify(mockOngoingCallListener).onOngoingCallStarted(anyBoolean())
+ verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
@Test
fun onEntryUpdated_notOngoingCallNotif_listenerNotNotified() {
notifCollectionListener.onEntryUpdated(createNotCallNotifEntry())
- verify(mockOngoingCallListener, never()).onOngoingCallStarted(anyBoolean())
+ verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
}
@Test
fun onEntryRemoved_ongoingCallNotif_listenerNotified() {
notifCollectionListener.onEntryRemoved(createOngoingCallNotifEntry(), REASON_USER_STOPPED)
- verify(mockOngoingCallListener).onOngoingCallEnded(anyBoolean())
+ verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
@Test
fun onEntryRemoved_notOngoingCallNotif_listenerNotNotified() {
notifCollectionListener.onEntryRemoved(createNotCallNotifEntry(), REASON_USER_STOPPED)
- verify(mockOngoingCallListener, never()).onOngoingCallEnded(anyBoolean())
+ verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean())
}
@Test
@@ -120,13 +146,26 @@ class OngoingCallControllerTest : SysuiTestCase() {
}
@Test
- fun hasOngoingCall_ongoingCallNotifSentAndChipViewSet_returnsTrue() {
+ fun hasOngoingCall_ongoingCallNotifSentAndCallAppNotVisible_returnsTrue() {
+ `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
+ .thenReturn(PROC_STATE_INVISIBLE)
+
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
assertThat(controller.hasOngoingCall()).isTrue()
}
@Test
+ fun hasOngoingCall_ongoingCallNotifSentButCallAppVisible_returnsFalse() {
+ `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
+ .thenReturn(PROC_STATE_VISIBLE)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ assertThat(controller.hasOngoingCall()).isFalse()
+ }
+
+ @Test
fun hasOngoingCall_ongoingCallNotifSentButInvalidChipView_returnsFalse() {
val invalidChipView = LinearLayout(context)
controller.setChipView(invalidChipView)
@@ -169,7 +208,52 @@ class OngoingCallControllerTest : SysuiTestCase() {
// Verify the listener was notified once for the initial call and again when the new view
// was set.
- verify(mockOngoingCallListener, times(2)).onOngoingCallStarted(anyBoolean())
+ verify(mockOngoingCallListener, times(2))
+ .onOngoingCallStateChanged(anyBoolean())
+ }
+
+ @Test
+ fun callProcessChangesToVisible_listenerNotified() {
+ // Start the call while the process is invisible.
+ `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
+ .thenReturn(PROC_STATE_INVISIBLE)
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java)
+ verify(mockIActivityManager).registerUidObserver(
+ captor.capture(), any(), any(), nullable(String::class.java))
+ val uidObserver = captor.value
+
+ // Update the process to visible.
+ uidObserver.onUidStateChanged(CALL_UID, PROC_STATE_VISIBLE, 0, 0)
+ 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())
+ }
+
+ @Test
+ fun callProcessChangesToInvisible_listenerNotified() {
+ // Start the call while the process is visible.
+ `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
+ .thenReturn(PROC_STATE_VISIBLE)
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ val captor = ArgumentCaptor.forClass(IUidObserver.Stub::class.java)
+ verify(mockIActivityManager).registerUidObserver(
+ captor.capture(), any(), any(), nullable(String::class.java))
+ val uidObserver = captor.value
+
+ // Update the process to invisible.
+ uidObserver.onUidStateChanged(CALL_UID, PROC_STATE_INVISIBLE, 0, 0)
+ 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())
}
private fun createOngoingCallNotifEntry(): NotificationEntry {
@@ -179,6 +263,7 @@ class OngoingCallControllerTest : SysuiTestCase() {
val contentIntent = mock(PendingIntent::class.java)
`when`(contentIntent.intent).thenReturn(mock(Intent::class.java))
notificationEntryBuilder.modifyNotification(context).setContentIntent(contentIntent)
+ notificationEntryBuilder.setUid(CALL_UID)
return notificationEntryBuilder.build()
}