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

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

Merge "[Ongoing Call Chip] Keep chip displayed on theme change." into sc-dev am: 4b537e91

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

Change-Id: I13399b0b357405a2e4749253b10f766e0fc3189f
parents d5a8384c 4b537e91
Loading
Loading
Loading
Loading
+4 −5
Original line number Diff line number Diff line
@@ -162,6 +162,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
        Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
        mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
        mClockView = mStatusBar.findViewById(R.id.clock);
        mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
        showSystemIconArea(false);
        showClock(false);
        initEmergencyCryptkeeperText();
@@ -182,7 +183,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
        super.onResume();
        mCommandQueue.addCallback(this);
        mStatusBarStateController.addCallback(this);
        mOngoingCallController.addCallback(mOngoingCallListener);
        initOngoingCallChip();
    }

    @Override
@@ -221,8 +222,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
        }
        statusBarCenteredIconArea.addView(mCenteredIconArea);

        initOngoingCallChip();

        // Default to showing until we know otherwise.
        showNotificationIconArea(false);
    }
@@ -273,7 +272,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
            state |= DISABLE_CLOCK;
        }

        if (mOngoingCallController.getHasOngoingCall()) {
        if (mOngoingCallController.hasOngoingCall()) {
            state |= DISABLE_NOTIFICATION_ICONS;
        }

@@ -448,7 +447,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
    }

    private void initOngoingCallChip() {
        mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
        mOngoingCallController.addCallback(mOngoingCallListener);
        mOngoingCallController.setChipView(mOngoingCallChip);
    }

+59 −27
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone.ongoingcall

import android.app.Notification
import android.app.Notification.CallStyle.CALL_TYPE_ONGOING
import android.content.Intent
import android.util.Log
import android.view.ViewGroup
import android.widget.Chronometer
@@ -44,8 +45,8 @@ class OngoingCallController @Inject constructor(
    private val activityStarter: ActivityStarter
) : CallbackController<OngoingCallListener> {

    var hasOngoingCall = false
        private set
    /** Null if there's no ongoing call. */
    private var ongoingCallInfo: OngoingCallInfo? = null
    private var chipView: ViewGroup? = null

    private val mListeners: MutableList<OngoingCallListener> = mutableListOf()
@@ -53,35 +54,16 @@ class OngoingCallController @Inject constructor(
    private val notifListener = object : NotifCollectionListener {
        override fun onEntryUpdated(entry: NotificationEntry) {
            if (isOngoingCallNotification(entry)) {
                val currentChipView = chipView
                val timeView =
                        currentChipView?.findViewById<Chronometer>(R.id.ongoing_call_chip_time)

                if (currentChipView != null && timeView != null) {
                    hasOngoingCall = true
                    val callStartTime = entry.sbn.notification.`when`
                    timeView.base = callStartTime -
                            System.currentTimeMillis() +
                            systemClock.elapsedRealtime()
                    timeView.start()

                    currentChipView.setOnClickListener {
                        activityStarter.postStartActivityDismissingKeyguard(
                                entry.sbn.notification.contentIntent.intent, 0,
                                ActivityLaunchAnimator.Controller.fromView(it))
                    }

                    mListeners.forEach { l -> l.onOngoingCallStarted(animate = true) }
                } else if (DEBUG) {
                    Log.w(TAG, "Ongoing call chip view could not be found; " +
                            "Not displaying chip in status bar")
                }
                ongoingCallInfo = OngoingCallInfo(
                entry.sbn.notification.`when`,
                        entry.sbn.notification.contentIntent.intent)
                updateChip()
            }
        }

        override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
            if (isOngoingCallNotification(entry)) {
                hasOngoingCall = false
                ongoingCallInfo = null
                mListeners.forEach { l -> l.onOngoingCallEnded(animate = true) }
            }
        }
@@ -93,10 +75,23 @@ class OngoingCallController @Inject constructor(
        }
    }

    fun setChipView(chipView: ViewGroup?) {
    /**
     * Sets the chip view that will contain ongoing call information.
     *
     * Should only be called from [CollapedStatusBarFragment].
     */
    fun setChipView(chipView: ViewGroup) {
        this.chipView = chipView
        if (hasOngoingCall()) {
            updateChip()
        }
    }

    /**
     * Returns true if there's an active ongoing call that can be displayed in a status bar chip.
     */
    fun hasOngoingCall(): Boolean = ongoingCallInfo != null

    override fun addCallback(listener: OngoingCallListener) {
        synchronized(mListeners) {
            if (!mListeners.contains(listener)) {
@@ -110,6 +105,43 @@ class OngoingCallController @Inject constructor(
            mListeners.remove(listener)
        }
    }

    private fun updateChip() {
        val currentOngoingCallInfo = ongoingCallInfo ?: return

        val currentChipView = chipView
        val timeView =
                currentChipView?.findViewById<Chronometer>(R.id.ongoing_call_chip_time)

        if (currentChipView != null && timeView != null) {
            timeView.base = currentOngoingCallInfo.callStartTime -
                    System.currentTimeMillis() +
                    systemClock.elapsedRealtime()
            timeView.start()

            currentChipView.setOnClickListener {
                activityStarter.postStartActivityDismissingKeyguard(
                        currentOngoingCallInfo.intent, 0,
                        ActivityLaunchAnimator.Controller.fromView(it))
            }

            mListeners.forEach { l -> l.onOngoingCallStarted(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 (DEBUG) {
                Log.w(TAG, "Ongoing call chip view could not be found; " +
                        "Not displaying chip in status bar")
            }
        }
    }

    private class OngoingCallInfo(
        val callStartTime: Long,
        val intent: Intent
    )
}

private fun isOngoingCallNotification(entry: NotificationEntry): Boolean {
+2 −2
Original line number Diff line number Diff line
@@ -184,7 +184,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
        Mockito.verify(mOngoingCallController).addCallback(ongoingCallListenerCaptor.capture());
        OngoingCallListener listener = Objects.requireNonNull(ongoingCallListenerCaptor.getValue());

        when(mOngoingCallController.getHasOngoingCall()).thenReturn(true);
        when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
        listener.onOngoingCallStarted(/* animate= */ false);

        assertEquals(View.VISIBLE,
@@ -205,7 +205,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
        Mockito.verify(mOngoingCallController).addCallback(ongoingCallListenerCaptor.capture());
        OngoingCallListener listener = Objects.requireNonNull(ongoingCallListenerCaptor.getValue());

        when(mOngoingCallController.getHasOngoingCall()).thenReturn(false);
        when(mOngoingCallController.hasOngoingCall()).thenReturn(false);
        listener.onOngoingCallEnded(/* animate= */ false);

        assertEquals(View.GONE,
+41 −7
Original line number Diff line number Diff line
@@ -19,12 +19,13 @@ package com.android.systemui.statusbar.phone.ongoingcall
import android.app.Notification
import android.app.PendingIntent
import android.app.Person
import android.content.Intent
import android.service.notification.NotificationListenerService.REASON_USER_STOPPED
import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.LayoutInflater
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.ActivityStarter
@@ -44,6 +45,7 @@ import org.mockito.Mock
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.MockitoAnnotations

@@ -114,22 +116,24 @@ class OngoingCallControllerTest : SysuiTestCase() {

    @Test
    fun hasOngoingCall_noOngoingCallNotifSent_returnsFalse() {
        assertThat(controller.hasOngoingCall).isFalse()
        assertThat(controller.hasOngoingCall()).isFalse()
    }

    @Test
    fun hasOngoingCall_ongoingCallNotifSentAndChipViewSet_returnsTrue() {
        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())

        assertThat(controller.hasOngoingCall).isTrue()
        assertThat(controller.hasOngoingCall()).isTrue()
    }

    @Test
    fun hasOngoingCall_ongoingCallNotifSentButNoChipView_returnsFalse() {
        controller.setChipView(null)
    fun hasOngoingCall_ongoingCallNotifSentButInvalidChipView_returnsFalse() {
        val invalidChipView = LinearLayout(context)
        controller.setChipView(invalidChipView)

        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())

        assertThat(controller.hasOngoingCall).isFalse()
        assertThat(controller.hasOngoingCall()).isFalse()
    }

    @Test
@@ -139,12 +143,42 @@ class OngoingCallControllerTest : SysuiTestCase() {
        notifCollectionListener.onEntryUpdated(ongoingCallNotifEntry)
        notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)

        assertThat(controller.hasOngoingCall).isFalse()
        assertThat(controller.hasOngoingCall()).isFalse()
    }

    /**
     * This test fakes a theme change during an ongoing call.
     *
     * When a theme change happens, [CollapsedStatusBarFragment] and its views get re-created, so
     * [OngoingCallController.setChipView] gets called with a new view. If there's an active ongoing
     * call when the theme changes, the new view needs to be updated with the call information.
     */
    @Test
    fun setChipView_whenHasOngoingCallIsTrue_listenerNotifiedWithNewView() {
        // Start an ongoing call.
        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())

        lateinit var newChipView: LinearLayout
        TestableLooper.get(this).runWithLooper {
            newChipView = LayoutInflater.from(mContext)
                    .inflate(R.layout.ongoing_call_chip, null) as LinearLayout
        }

        // 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)).onOngoingCallStarted(anyBoolean())
    }

    private fun createOngoingCallNotifEntry(): NotificationEntry {
        val notificationEntryBuilder = NotificationEntryBuilder()
        notificationEntryBuilder.modifyNotification(context).style = ongoingCallStyle

        val contentIntent = mock(PendingIntent::class.java)
        `when`(contentIntent.intent).thenReturn(mock(Intent::class.java))
        notificationEntryBuilder.modifyNotification(context).setContentIntent(contentIntent)
        return notificationEntryBuilder.build()
    }