Merge "[Ongoing Call] Hide chip when app is in foreground." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
6afee4990a
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,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)
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user