Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +82 −68 Original line number Diff line number Diff line Loading @@ -69,13 +69,10 @@ class OngoingCallController @Inject constructor( private var isFullscreen: Boolean = false /** Non-null if there's an active call notification. */ private var callNotificationInfo: CallNotificationInfo? = null /** True if the application managing the call is visible to the user. */ private var isCallAppVisible: Boolean = false private var chipView: View? = null private var uidObserver: IUidObserver.Stub? = null private val mListeners: MutableList<OngoingCallListener> = mutableListOf() private val uidObserver = CallAppUidObserver() private val notifListener = object : NotifCollectionListener { // Temporary workaround for b/178406514 for testing purposes. // Loading Loading @@ -160,7 +157,7 @@ class OngoingCallController @Inject constructor( fun hasOngoingCall(): Boolean { return callNotificationInfo?.isOngoing == true && // When the user is in the phone app, don't show the chip. !isCallAppVisible !uidObserver.isCallAppVisible } override fun addCallback(listener: OngoingCallListener) { Loading Loading @@ -196,7 +193,7 @@ class OngoingCallController @Inject constructor( } updateChipClickListener() setUpUidObserver(currentCallNotificationInfo) uidObserver.registerWithUid(currentCallNotificationInfo.uid) if (!currentCallNotificationInfo.statusBarSwipedAway) { statusBarWindowController.ifPresent { it.setOngoingProcessRequiresStatusBarVisible(true) Loading Loading @@ -240,64 +237,6 @@ class OngoingCallController @Inject constructor( } } /** * Sets up an [IUidObserver] to monitor the status of the application managing the ongoing call. */ private fun setUpUidObserver(currentCallNotificationInfo: CallNotificationInfo) { try { isCallAppVisible = isProcessVisibleToUser( iActivityManager.getUidProcessState( currentCallNotificationInfo.uid, context.opPackageName ) ) } catch (se: SecurityException) { Log.e(TAG, "Security exception when trying to get process state: $se") return } if (uidObserver != null) { iActivityManager.unregisterUidObserver(uidObserver) } uidObserver = object : IUidObserver.Stub() { override fun onUidStateChanged( uid: Int, procState: Int, procStateSeq: Long, capability: Int ) { if (uid == currentCallNotificationInfo.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) {} } try { iActivityManager.registerUidObserver( uidObserver, ActivityManager.UID_OBSERVER_PROCSTATE, ActivityManager.PROCESS_STATE_UNKNOWN, context.opPackageName ) } catch (se: SecurityException) { Log.e(TAG, "Security exception when trying to register uid observer: $se") return } } /** 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 Loading @@ -321,9 +260,7 @@ class OngoingCallController @Inject constructor( statusBarWindowController.ifPresent { it.setOngoingProcessRequiresStatusBarVisible(false) } swipeStatusBarAwayGestureHandler.ifPresent { it.removeOnGestureDetectedCallback(TAG) } mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) } if (uidObserver != null) { iActivityManager.unregisterUidObserver(uidObserver) } uidObserver.unregister() } /** Tear down anything related to the chip view to prevent leaks. */ Loading Loading @@ -380,7 +317,84 @@ class OngoingCallController @Inject constructor( override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.println("Active call notification: $callNotificationInfo") pw.println("Call app visible: $isCallAppVisible") pw.println("Call app visible: ${uidObserver.isCallAppVisible}") } /** Our implementation of a [IUidObserver]. */ inner class CallAppUidObserver : IUidObserver.Stub() { /** True if the application managing the call is visible to the user. */ var isCallAppVisible: Boolean = false private set /** The UID of the application managing the call. Null if there is no active call. */ private var callAppUid: Int? = null /** * True if this observer is currently registered with the activity manager and false * otherwise. */ private var isRegistered = false /** Register this observer with the activity manager and the given [uid]. */ fun registerWithUid(uid: Int) { if (callAppUid == uid) { return } callAppUid = uid try { isCallAppVisible = isProcessVisibleToUser( iActivityManager.getUidProcessState(uid, context.opPackageName) ) if (isRegistered) { return } iActivityManager.registerUidObserver( uidObserver, ActivityManager.UID_OBSERVER_PROCSTATE, ActivityManager.PROCESS_STATE_UNKNOWN, context.opPackageName ) isRegistered = true } catch (se: SecurityException) { Log.e(TAG, "Security exception when trying to set up uid observer: $se") } } /** Unregister this observer with the activity manager. */ fun unregister() { callAppUid = null isRegistered = false iActivityManager.unregisterUidObserver(uidObserver) } override fun onUidStateChanged( uid: Int, procState: Int, procStateSeq: Long, capability: Int ) { val currentCallAppUid = callAppUid ?: return if (uid != currentCallAppUid) { return } 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) {} } } Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +5 −7 Original line number Diff line number Diff line Loading @@ -205,17 +205,15 @@ class OngoingCallControllerTest : SysuiTestCase() { /** Regression test for b/194731244. */ @Test fun onEntryUpdated_calledManyTimes_uidObserverUnregisteredManyTimes() { val numCalls = 4 for (i in 0 until numCalls) { fun onEntryUpdated_calledManyTimes_uidObserverOnlyRegisteredOnce() { for (i in 0 until 4) { // Re-create the notification each time so that it's considered a different object and // observers will get re-registered (and hopefully unregistered). // will re-trigger the whole flow. notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) } // There should be 1 observer still registered, so we should unregister n-1 times. verify(mockIActivityManager, times(numCalls - 1)).unregisterUidObserver(any()) verify(mockIActivityManager, times(1)) .registerUidObserver(any(), any(), any(), any()) } /** Regression test for b/216248574. */ Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +82 −68 Original line number Diff line number Diff line Loading @@ -69,13 +69,10 @@ class OngoingCallController @Inject constructor( private var isFullscreen: Boolean = false /** Non-null if there's an active call notification. */ private var callNotificationInfo: CallNotificationInfo? = null /** True if the application managing the call is visible to the user. */ private var isCallAppVisible: Boolean = false private var chipView: View? = null private var uidObserver: IUidObserver.Stub? = null private val mListeners: MutableList<OngoingCallListener> = mutableListOf() private val uidObserver = CallAppUidObserver() private val notifListener = object : NotifCollectionListener { // Temporary workaround for b/178406514 for testing purposes. // Loading Loading @@ -160,7 +157,7 @@ class OngoingCallController @Inject constructor( fun hasOngoingCall(): Boolean { return callNotificationInfo?.isOngoing == true && // When the user is in the phone app, don't show the chip. !isCallAppVisible !uidObserver.isCallAppVisible } override fun addCallback(listener: OngoingCallListener) { Loading Loading @@ -196,7 +193,7 @@ class OngoingCallController @Inject constructor( } updateChipClickListener() setUpUidObserver(currentCallNotificationInfo) uidObserver.registerWithUid(currentCallNotificationInfo.uid) if (!currentCallNotificationInfo.statusBarSwipedAway) { statusBarWindowController.ifPresent { it.setOngoingProcessRequiresStatusBarVisible(true) Loading Loading @@ -240,64 +237,6 @@ class OngoingCallController @Inject constructor( } } /** * Sets up an [IUidObserver] to monitor the status of the application managing the ongoing call. */ private fun setUpUidObserver(currentCallNotificationInfo: CallNotificationInfo) { try { isCallAppVisible = isProcessVisibleToUser( iActivityManager.getUidProcessState( currentCallNotificationInfo.uid, context.opPackageName ) ) } catch (se: SecurityException) { Log.e(TAG, "Security exception when trying to get process state: $se") return } if (uidObserver != null) { iActivityManager.unregisterUidObserver(uidObserver) } uidObserver = object : IUidObserver.Stub() { override fun onUidStateChanged( uid: Int, procState: Int, procStateSeq: Long, capability: Int ) { if (uid == currentCallNotificationInfo.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) {} } try { iActivityManager.registerUidObserver( uidObserver, ActivityManager.UID_OBSERVER_PROCSTATE, ActivityManager.PROCESS_STATE_UNKNOWN, context.opPackageName ) } catch (se: SecurityException) { Log.e(TAG, "Security exception when trying to register uid observer: $se") return } } /** 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 Loading @@ -321,9 +260,7 @@ class OngoingCallController @Inject constructor( statusBarWindowController.ifPresent { it.setOngoingProcessRequiresStatusBarVisible(false) } swipeStatusBarAwayGestureHandler.ifPresent { it.removeOnGestureDetectedCallback(TAG) } mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) } if (uidObserver != null) { iActivityManager.unregisterUidObserver(uidObserver) } uidObserver.unregister() } /** Tear down anything related to the chip view to prevent leaks. */ Loading Loading @@ -380,7 +317,84 @@ class OngoingCallController @Inject constructor( override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.println("Active call notification: $callNotificationInfo") pw.println("Call app visible: $isCallAppVisible") pw.println("Call app visible: ${uidObserver.isCallAppVisible}") } /** Our implementation of a [IUidObserver]. */ inner class CallAppUidObserver : IUidObserver.Stub() { /** True if the application managing the call is visible to the user. */ var isCallAppVisible: Boolean = false private set /** The UID of the application managing the call. Null if there is no active call. */ private var callAppUid: Int? = null /** * True if this observer is currently registered with the activity manager and false * otherwise. */ private var isRegistered = false /** Register this observer with the activity manager and the given [uid]. */ fun registerWithUid(uid: Int) { if (callAppUid == uid) { return } callAppUid = uid try { isCallAppVisible = isProcessVisibleToUser( iActivityManager.getUidProcessState(uid, context.opPackageName) ) if (isRegistered) { return } iActivityManager.registerUidObserver( uidObserver, ActivityManager.UID_OBSERVER_PROCSTATE, ActivityManager.PROCESS_STATE_UNKNOWN, context.opPackageName ) isRegistered = true } catch (se: SecurityException) { Log.e(TAG, "Security exception when trying to set up uid observer: $se") } } /** Unregister this observer with the activity manager. */ fun unregister() { callAppUid = null isRegistered = false iActivityManager.unregisterUidObserver(uidObserver) } override fun onUidStateChanged( uid: Int, procState: Int, procStateSeq: Long, capability: Int ) { val currentCallAppUid = callAppUid ?: return if (uid != currentCallAppUid) { return } 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) {} } } Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +5 −7 Original line number Diff line number Diff line Loading @@ -205,17 +205,15 @@ class OngoingCallControllerTest : SysuiTestCase() { /** Regression test for b/194731244. */ @Test fun onEntryUpdated_calledManyTimes_uidObserverUnregisteredManyTimes() { val numCalls = 4 for (i in 0 until numCalls) { fun onEntryUpdated_calledManyTimes_uidObserverOnlyRegisteredOnce() { for (i in 0 until 4) { // Re-create the notification each time so that it's considered a different object and // observers will get re-registered (and hopefully unregistered). // will re-trigger the whole flow. notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) } // There should be 1 observer still registered, so we should unregister n-1 times. verify(mockIActivityManager, times(numCalls - 1)).unregisterUidObserver(any()) verify(mockIActivityManager, times(1)) .registerUidObserver(any(), any(), any(), any()) } /** Regression test for b/216248574. */ Loading