Loading packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +7 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; } Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +1 −6 Original line number Diff line number Diff line Loading @@ -105,12 +105,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); } }; Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +71 −8 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 /** Loading @@ -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() Loading @@ -68,7 +78,8 @@ class OngoingCallController @Inject constructor( if (isOngoingCallNotification(entry)) { ongoingCallInfo = OngoingCallInfo( entry.sbn.notification.`when`, entry.sbn.notification.contentIntent.intent) entry.sbn.notification.contentIntent.intent, entry.sbn.uid) updateChip() } } Loading @@ -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) } } } } Loading @@ -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) { Loading Loading @@ -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. Loading @@ -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 ) } Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallListener.kt +7 −5 Original line number Diff line number Diff line Loading @@ -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) } packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +95 −10 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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) Loading @@ -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 Loading @@ -120,12 +146,25 @@ 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) Loading Loading @@ -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 { Loading @@ -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() } Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +7 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; } Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +1 −6 Original line number Diff line number Diff line Loading @@ -105,12 +105,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); } }; Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +71 −8 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 /** Loading @@ -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() Loading @@ -68,7 +78,8 @@ class OngoingCallController @Inject constructor( if (isOngoingCallNotification(entry)) { ongoingCallInfo = OngoingCallInfo( entry.sbn.notification.`when`, entry.sbn.notification.contentIntent.intent) entry.sbn.notification.contentIntent.intent, entry.sbn.uid) updateChip() } } Loading @@ -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) } } } } Loading @@ -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) { Loading Loading @@ -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. Loading @@ -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 ) } Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallListener.kt +7 −5 Original line number Diff line number Diff line Loading @@ -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) }
packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +95 −10 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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) Loading @@ -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 Loading @@ -120,12 +146,25 @@ 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) Loading Loading @@ -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 { Loading @@ -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() } Loading