Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b05c06a0 authored by Caitlin Cassidy's avatar Caitlin Cassidy Committed by Automerger Merge Worker
Browse files

Merge "[Ongoing Call] Hide chip when app is in foreground." into sc-dev am: 6afee499

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14387133

Change-Id: I4359d1a1c05c30652988520c8e3c5889b91cf60b
parents 676e76e4 6afee499
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -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;
    }
+1 −6
Original line number Diff line number Diff line
@@ -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);
        }
    };
+71 −8
Original line number Diff line number Diff line
@@ -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()

@@ -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()
            }
        }
@@ -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
    )
}

+7 −5
Original line number Diff line number Diff line
@@ -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)
}
+95 −10
Original line number Diff line number Diff line
@@ -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()
    }