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

Commit c4c7c0f6 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB][Screen Chips] Refactor OngoingCallRepo to use model instead of boolean.

In order to show the ongoing call chip with all the other new chips, we
need some additional information from the OngoingCallRepository besides
just "whether there's a call or not". This updates the repo to return a
model representing the call state instead of just a boolean.

Also, it seems OngoingCallController + Test needed a full reformatting
:D

Bug: 332662551
Test: atest OngoingCallControllerTest OngoingCallRepositoryTest
StatusBarModeRepositoryImplTest
Test: With com.android.systemui.status_bar_screen_sharing_chips flag
off, start an ongoing call -> verify old chip shows up. Open a
fullscreen app and verify the status bar still appears with a
semi-transparent background (that's the only thing the
OngoingCallRepository is actually used for at the moment)
Flag: NONE Refactor that would be more confusing if it was flag-guarded

Change-Id: I35bd5fd730797b42214e6aa2cc034dc5da7d96e5
parent d26363c4
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import com.android.systemui.statusbar.phone.BoundsPair
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -224,8 +225,8 @@ constructor(
                modifiedStatusBarAttributes,
                isTransientShown,
                isInFullscreenMode,
                ongoingCallRepository.hasOngoingCall,
            ) { modifiedAttributes, isTransientShown, isInFullscreenMode, hasOngoingCall ->
                ongoingCallRepository.ongoingCallState,
            ) { modifiedAttributes, isTransientShown, isInFullscreenMode, ongoingCallState ->
                if (modifiedAttributes == null) {
                    null
                } else {
@@ -234,7 +235,7 @@ constructor(
                            modifiedAttributes.appearance,
                            isTransientShown,
                            isInFullscreenMode,
                            hasOngoingCall,
                            hasOngoingCall = ongoingCallState is OngoingCallModel.InCall,
                        )
                    StatusBarAppearance(
                        statusBarMode,
+107 −78
Original line number Diff line number Diff line
@@ -29,35 +29,36 @@ import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
import com.android.systemui.res.R
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.time.SystemClock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.io.PrintWriter
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

/**
 * A controller to handle the ongoing call chip in the collapsed status bar.
 */
/** A controller to handle the ongoing call chip in the collapsed status bar. */
@SysUISingleton
class OngoingCallController @Inject constructor(
class OngoingCallController
@Inject
constructor(
    @Application private val scope: CoroutineScope,
    private val context: Context,
    private val ongoingCallRepository: OngoingCallRepository,
@@ -79,12 +80,13 @@ class OngoingCallController @Inject constructor(

    private val mListeners: MutableList<OngoingCallListener> = mutableListOf()
    private val uidObserver = CallAppUidObserver()
    private val notifListener = object : NotifCollectionListener {
    private val notifListener =
        object : NotifCollectionListener {
            // Temporary workaround for b/178406514 for testing purposes.
            //
        // b/178406514 means that posting an incoming call notif then updating it to an ongoing call
        // notif does not work (SysUI never receives the update). This workaround allows us to
        // trigger the ongoing call chip when an ongoing call notif is *added* rather than
            // b/178406514 means that posting an incoming call notif then updating it to an ongoing
            // call notif does not work (SysUI never receives the update). This workaround allows us
            // to trigger the ongoing call chip when an ongoing call notif is *added* rather than
            // *updated*, allowing us to test the chip.
            //
            // TODO(b/183229367): Remove this function override when b/178406514 is fixed.
@@ -93,18 +95,24 @@ class OngoingCallController @Inject constructor(
            }

            override fun onEntryUpdated(entry: NotificationEntry) {
            // We have a new call notification or our existing call notification has been updated.
                // We have a new call notification or our existing call notification has been
                // updated.
                // TODO(b/183229367): This likely won't work if you take a call from one app then
                //  switch to a call from another app.
            if (callNotificationInfo == null && isCallNotification(entry) ||
                    (entry.sbn.key == callNotificationInfo?.key)) {
                val newOngoingCallInfo = CallNotificationInfo(
                if (
                    callNotificationInfo == null && isCallNotification(entry) ||
                        (entry.sbn.key == callNotificationInfo?.key)
                ) {
                    val newOngoingCallInfo =
                        CallNotificationInfo(
                            entry.sbn.key,
                            entry.sbn.notification.getWhen(),
                            entry.sbn.notification.contentIntent,
                            entry.sbn.uid,
                            entry.sbn.notification.extras.getInt(
                                Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING,
                                Notification.EXTRA_CALL_TYPE,
                                -1
                            ) == CALL_TYPE_ONGOING,
                            statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
                        )
                    if (newOngoingCallInfo == callNotificationInfo) {
@@ -173,6 +181,19 @@ class OngoingCallController @Inject constructor(
            !uidObserver.isCallAppVisible
    }

    /** Creates the right [OngoingCallModel] based on the call state. */
    private fun getOngoingCallModel(): OngoingCallModel {
        if (hasOngoingCall()) {
            val currentInfo =
                callNotificationInfo
                    // This shouldn't happen, but protect against it in case
                    ?: return OngoingCallModel.NoCall
            return OngoingCallModel.InCall(currentInfo.callStartTime)
        } else {
            return OngoingCallModel.NoCall
        }
    }

    override fun addCallback(listener: OngoingCallListener) {
        synchronized(mListeners) {
            if (!mListeners.contains(listener)) {
@@ -182,9 +203,7 @@ class OngoingCallController @Inject constructor(
    }

    override fun removeCallback(listener: OngoingCallListener) {
        synchronized(mListeners) {
            mListeners.remove(listener)
        }
        synchronized(mListeners) { mListeners.remove(listener) }
    }

    private fun updateChip() {
@@ -196,8 +215,8 @@ class OngoingCallController @Inject constructor(
        if (currentChipView != null && timeView != null) {
            if (currentCallNotificationInfo.hasValidStartTime()) {
                timeView.setShouldHideText(false)
                timeView.base = currentCallNotificationInfo.callStartTime -
                        systemClock.currentTimeMillis() +
                timeView.base =
                    currentCallNotificationInfo.callStartTime - systemClock.currentTimeMillis() +
                        systemClock.elapsedRealtime()
                timeView.start()
            } else {
@@ -218,14 +237,19 @@ class OngoingCallController @Inject constructor(
            callNotificationInfo = null

            if (DEBUG) {
                Log.w(TAG, "Ongoing call chip view could not be found; " +
                        "Not displaying chip in status bar")
                Log.w(
                    TAG,
                    "Ongoing call chip view could not be found; " +
                        "Not displaying chip in status bar"
                )
            }
        }
    }

    private fun updateChipClickListener() {
        if (callNotificationInfo == null) { return }
        if (callNotificationInfo == null) {
            return
        }
        val currentChipView = chipView
        val backgroundView =
            currentChipView?.findViewById<View>(R.id.ongoing_activity_chip_background)
@@ -237,7 +261,8 @@ class OngoingCallController @Inject constructor(
                    intent,
                    ActivityTransitionAnimator.Controller.fromView(
                        backgroundView,
                        InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
                        InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
                    )
                )
            }
        }
@@ -249,9 +274,11 @@ class OngoingCallController @Inject constructor(
    }

    private fun updateGestureListening() {
        if (callNotificationInfo == null ||
        if (
            callNotificationInfo == null ||
                callNotificationInfo?.statusBarSwipedAway == true ||
            !isFullscreen) {
                !isFullscreen
        ) {
            swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
        } else {
            swipeStatusBarAwayGestureHandler.addOnGestureDetectedCallback(TAG) { _ ->
@@ -270,8 +297,7 @@ class OngoingCallController @Inject constructor(
    }

    /** Tear down anything related to the chip view to prevent leaks. */
    @VisibleForTesting
    fun tearDownChipView() = chipView?.getTimeView()?.stop()
    @VisibleForTesting fun tearDownChipView() = chipView?.getTimeView()?.stop()

    private fun View.getTimeView(): ChipChronometer? {
        return this.findViewById(R.id.ongoing_activity_chip_time)
@@ -286,14 +312,16 @@ class OngoingCallController @Inject constructor(
     * detected.
     */
    private fun onSwipeAwayGestureDetected() {
        if (DEBUG) { Log.d(TAG, "Swipe away gesture detected") }
        if (DEBUG) {
            Log.d(TAG, "Swipe away gesture detected")
        }
        callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
        statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(false)
        swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
    }

    private fun sendStateChangeEvent() {
        ongoingCallRepository.setHasOngoingCall(hasOngoingCall())
        ongoingCallRepository.setOngoingCallState(getOngoingCallModel())
        mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
    }

@@ -308,8 +336,8 @@ class OngoingCallController @Inject constructor(
        val statusBarSwipedAway: Boolean
    ) {
        /**
         * Returns true if the notification information has a valid call start time.
         * See b/192379214.
         * Returns true if the notification information has a valid call start time. See
         * b/192379214.
         */
        fun hasValidStartTime(): Boolean = callStartTime > 0
    }
@@ -342,7 +370,8 @@ class OngoingCallController @Inject constructor(
            callAppUid = uid

            try {
                isCallAppVisible = isProcessVisibleToUser(
                isCallAppVisible =
                    isProcessVisibleToUser(
                        iActivityManager.getUidProcessState(uid, context.opPackageName)
                    )
                if (isRegistered) {
+33 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.statusbar.phone.ongoingcall.data.model

/** Represents the state of any ongoing calls. */
sealed interface OngoingCallModel {
    /** There is no ongoing call. */
    data object NoCall : OngoingCallModel

    /**
     * There *is* an ongoing call.
     *
     * @property startTimeMs the time that the phone call started, based on the notification's
     *   `when` field. Importantly, this time is relative to
     *   [com.android.systemui.util.time.SystemClock.currentTimeMillis], **not**
     *   [com.android.systemui.util.time.SystemClock.elapsedRealtime].
     */
    data class InCall(val startTimeMs: Long) : OngoingCallModel
}
+7 −6
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone.ongoingcall.data.repository

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -32,15 +33,15 @@ import kotlinx.coroutines.flow.asStateFlow
 */
@SysUISingleton
class OngoingCallRepository @Inject constructor() {
    private val _hasOngoingCall = MutableStateFlow(false)
    /** True if there's currently an ongoing call notification and false otherwise. */
    val hasOngoingCall: StateFlow<Boolean> = _hasOngoingCall.asStateFlow()
    private val _ongoingCallState = MutableStateFlow<OngoingCallModel>(OngoingCallModel.NoCall)
    /** The current ongoing call state. */
    val ongoingCallState: StateFlow<OngoingCallModel> = _ongoingCallState.asStateFlow()

    /**
     * Sets whether there's currently an ongoing call notification. Should only be set from
     * Sets the current ongoing call state, based on notifications. Should only be set from
     * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController].
     */
    fun setHasOngoingCall(hasOngoingCall: Boolean) {
        _hasOngoingCall.value = hasOngoingCall
    fun setOngoingCallState(state: OngoingCallModel) {
        _ongoingCallState.value = state
    }
}
+4 −3
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import com.android.systemui.statusbar.phone.LetterboxAppearance
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import com.android.systemui.statusbar.phone.ongoingcall.data.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -390,7 +391,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
        testScope.runTest {
            val latest by collectLastValue(underTest.statusBarAppearance)

            ongoingCallRepository.setHasOngoingCall(true)
            ongoingCallRepository.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 34))
            onSystemBarAttributesChanged(
                requestedVisibleTypes = WindowInsets.Type.navigationBars(),
            )
@@ -403,7 +404,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
        testScope.runTest {
            val latest by collectLastValue(underTest.statusBarAppearance)

            ongoingCallRepository.setHasOngoingCall(true)
            ongoingCallRepository.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 789))
            onSystemBarAttributesChanged(
                requestedVisibleTypes = WindowInsets.Type.statusBars(),
                appearance = APPEARANCE_OPAQUE_STATUS_BARS,
@@ -417,7 +418,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
        testScope.runTest {
            val latest by collectLastValue(underTest.statusBarAppearance)

            ongoingCallRepository.setHasOngoingCall(false)
            ongoingCallRepository.setOngoingCallState(OngoingCallModel.NoCall)
            onSystemBarAttributesChanged(
                requestedVisibleTypes = WindowInsets.Type.navigationBars(),
                appearance = APPEARANCE_OPAQUE_STATUS_BARS,
Loading