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

This commit is contained in:
Caitlin Cassidy
2021-04-19 14:53:07 +00:00
committed by Android (Google) Code Review
4 changed files with 106 additions and 41 deletions

View File

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

View File

@@ -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 {

View File

@@ -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,

View File

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