Rebind to ControlsProviderService if package updates
If we are bound to a ControlsProviderService and the package is updated, re-bind to it using the same flags after it has finished updating. Test: atest PackageUpdateMonitorTest Test: atest ControlsProviderLifecycleManagerTest Test: manual, reinstall app using adb Fixes: 274458990 Change-Id: I6f3bcc01695f23dcca6cbaca60255157bddbfbe4
This commit is contained in:
@@ -41,7 +41,8 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
private val context: Context,
|
||||
@Background private val backgroundExecutor: DelayableExecutor,
|
||||
private val lazyController: Lazy<ControlsController>,
|
||||
userTracker: UserTracker
|
||||
private val packageUpdateMonitorFactory: PackageUpdateMonitor.Factory,
|
||||
userTracker: UserTracker,
|
||||
) : ControlsBindingController {
|
||||
|
||||
companion object {
|
||||
@@ -93,7 +94,8 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
backgroundExecutor,
|
||||
actionCallbackService,
|
||||
currentUser,
|
||||
component
|
||||
component,
|
||||
packageUpdateMonitorFactory
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.controls.controller
|
||||
|
||||
import android.annotation.WorkerThread
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -23,7 +24,6 @@ import android.content.ServiceConnection
|
||||
import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import android.os.UserHandle
|
||||
import android.service.controls.ControlsProviderService
|
||||
import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
|
||||
@@ -38,6 +38,7 @@ import android.util.Log
|
||||
import com.android.internal.annotations.GuardedBy
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* Manager for the lifecycle of the connection to a given [ControlsProviderService].
|
||||
@@ -45,6 +46,9 @@ import java.util.concurrent.TimeUnit
|
||||
* This class handles binding and unbinding and requests to the service. The class will queue
|
||||
* requests until the service is connected and dispatch them then.
|
||||
*
|
||||
* If the provider app is updated, and we are currently bound to it, it will try to rebind after
|
||||
* update is completed.
|
||||
*
|
||||
* @property context A SystemUI context for binding to the services
|
||||
* @property executor A delayable executor for posting timeouts
|
||||
* @property actionCallbackService a callback interface to hand the remote service for sending
|
||||
@@ -59,22 +63,22 @@ class ControlsProviderLifecycleManager(
|
||||
private val executor: DelayableExecutor,
|
||||
private val actionCallbackService: IControlsActionCallback.Stub,
|
||||
val user: UserHandle,
|
||||
val componentName: ComponentName
|
||||
) : IBinder.DeathRecipient {
|
||||
val componentName: ComponentName,
|
||||
packageUpdateMonitorFactory: PackageUpdateMonitor.Factory,
|
||||
) {
|
||||
|
||||
val token: IBinder = Binder()
|
||||
private var requiresBound = false
|
||||
@GuardedBy("queuedServiceMethods")
|
||||
private val queuedServiceMethods: MutableSet<ServiceMethod> = ArraySet()
|
||||
private var wrapper: ServiceWrapper? = null
|
||||
private var bindTryCount = 0
|
||||
private val TAG = javaClass.simpleName
|
||||
private var onLoadCanceller: Runnable? = null
|
||||
|
||||
private var lastForPanel = false
|
||||
|
||||
companion object {
|
||||
private const val BIND_RETRY_DELAY = 1000L // ms
|
||||
private const val LOAD_TIMEOUT_SECONDS = 20L // seconds
|
||||
private const val MAX_BIND_RETRIES = 5
|
||||
private const val DEBUG = true
|
||||
private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or
|
||||
Context.BIND_NOT_PERCEPTIBLE
|
||||
@@ -91,60 +95,56 @@ class ControlsProviderLifecycleManager(
|
||||
})
|
||||
}
|
||||
|
||||
private fun bindService(bind: Boolean, forPanel: Boolean = false) {
|
||||
executor.execute {
|
||||
requiresBound = bind
|
||||
if (bind) {
|
||||
if (bindTryCount != MAX_BIND_RETRIES && wrapper == null) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Binding service $intent")
|
||||
}
|
||||
bindTryCount++
|
||||
try {
|
||||
val flags = if (forPanel) BIND_FLAGS_PANEL else BIND_FLAGS
|
||||
val bound = context
|
||||
.bindServiceAsUser(intent, serviceConnection, flags, user)
|
||||
if (!bound) {
|
||||
context.unbindService(serviceConnection)
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to bind to service", e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Unbinding service $intent")
|
||||
}
|
||||
bindTryCount = 0
|
||||
wrapper?.run {
|
||||
context.unbindService(serviceConnection)
|
||||
}
|
||||
wrapper = null
|
||||
private val packageUpdateMonitor = packageUpdateMonitorFactory.create(
|
||||
user,
|
||||
componentName.packageName,
|
||||
) {
|
||||
if (requiresBound) {
|
||||
// Let's unbind just in case. onBindingDied should have been called and unbound before.
|
||||
executor.execute {
|
||||
unbindAndCleanup("package updated")
|
||||
bindService(true, lastForPanel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindService(bind: Boolean, forPanel: Boolean = false) {
|
||||
executor.execute {
|
||||
bindServiceBackground(bind, forPanel)
|
||||
}
|
||||
}
|
||||
|
||||
private val serviceConnection = object : ServiceConnection {
|
||||
|
||||
val connected = AtomicBoolean(false)
|
||||
|
||||
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||
if (DEBUG) Log.d(TAG, "onServiceConnected $name")
|
||||
bindTryCount = 0
|
||||
wrapper = ServiceWrapper(IControlsProvider.Stub.asInterface(service))
|
||||
try {
|
||||
service.linkToDeath(this@ControlsProviderLifecycleManager, 0)
|
||||
} catch (_: RemoteException) {}
|
||||
packageUpdateMonitor.startMonitoring()
|
||||
handlePendingServiceMethods()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
if (DEBUG) Log.d(TAG, "onServiceDisconnected $name")
|
||||
wrapper = null
|
||||
bindService(false)
|
||||
// No need to call unbind. We may get a new `onServiceConnected`
|
||||
}
|
||||
|
||||
override fun onNullBinding(name: ComponentName?) {
|
||||
if (DEBUG) Log.d(TAG, "onNullBinding $name")
|
||||
wrapper = null
|
||||
context.unbindService(this)
|
||||
executor.execute {
|
||||
unbindAndCleanup("null binding")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindingDied(name: ComponentName?) {
|
||||
super.onBindingDied(name)
|
||||
if (DEBUG) Log.d(TAG, "onBindingDied $name")
|
||||
executor.execute {
|
||||
unbindAndCleanup("binder died")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,14 +159,55 @@ class ControlsProviderLifecycleManager(
|
||||
}
|
||||
}
|
||||
|
||||
override fun binderDied() {
|
||||
if (wrapper == null) return
|
||||
wrapper = null
|
||||
if (requiresBound) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "binderDied")
|
||||
@WorkerThread
|
||||
private fun bindServiceBackground(bind: Boolean, forPanel: Boolean = true) {
|
||||
requiresBound = bind
|
||||
if (bind) {
|
||||
if (wrapper == null) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Binding service $intent")
|
||||
}
|
||||
try {
|
||||
lastForPanel = forPanel
|
||||
val flags = if (forPanel) BIND_FLAGS_PANEL else BIND_FLAGS
|
||||
var bound = false
|
||||
if (serviceConnection.connected.compareAndSet(false, true)) {
|
||||
bound = context
|
||||
.bindServiceAsUser(intent, serviceConnection, flags, user)
|
||||
}
|
||||
if (!bound) {
|
||||
Log.d(TAG, "Couldn't bind to $intent")
|
||||
doUnbind()
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to bind to service", e)
|
||||
// Couldn't even bind. Let's reset the connected value
|
||||
serviceConnection.connected.set(false)
|
||||
}
|
||||
}
|
||||
// Try rebinding some time later
|
||||
} else {
|
||||
unbindAndCleanup("unbind requested")
|
||||
packageUpdateMonitor.stopMonitoring()
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun unbindAndCleanup(reason: String) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Unbinding service $intent. Reason: $reason")
|
||||
}
|
||||
wrapper = null
|
||||
try {
|
||||
doUnbind()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "Failed to unbind service", e)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun doUnbind() {
|
||||
if (serviceConnection.connected.compareAndSet(true, false)) {
|
||||
context.unbindService(serviceConnection)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,7 +354,7 @@ class ControlsProviderLifecycleManager(
|
||||
fun run() {
|
||||
if (!callWrapper()) {
|
||||
queueServiceMethod(this)
|
||||
binderDied()
|
||||
executor.execute { unbindAndCleanup("couldn't call through binder") }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.controls.controller
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.UserHandle
|
||||
import com.android.internal.content.PackageMonitor
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/** [PackageMonitor] that tracks when [packageName] has finished updating for user [user]. */
|
||||
class PackageUpdateMonitor
|
||||
@AssistedInject
|
||||
constructor(
|
||||
@Assisted private val user: UserHandle,
|
||||
@Assisted private val packageName: String,
|
||||
@Assisted private val callback: Runnable,
|
||||
@Background private val bgHandler: Handler,
|
||||
@Application private val context: Context,
|
||||
) : PackageMonitor() {
|
||||
|
||||
private val monitoring = AtomicBoolean(false)
|
||||
|
||||
@AssistedFactory
|
||||
fun interface Factory {
|
||||
/**
|
||||
* Create a [PackageUpdateMonitor] for a given [user] and [packageName]. It will run
|
||||
* [callback] every time the package finishes updating.
|
||||
*/
|
||||
fun create(user: UserHandle, packageName: String, callback: Runnable): PackageUpdateMonitor
|
||||
}
|
||||
|
||||
/** Start monitoring for package updates. No-op if already monitoring. */
|
||||
fun startMonitoring() {
|
||||
if (monitoring.compareAndSet(/* expected */ false, /* new */ true)) {
|
||||
register(context, user, false, bgHandler)
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop monitoring for package updates. No-op if not monitoring. */
|
||||
fun stopMonitoring() {
|
||||
if (monitoring.compareAndSet(/* expected */ true, /* new */ false)) {
|
||||
unregister()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the package and the user match the ones for this [PackageUpdateMonitor], it will run
|
||||
* [callback].
|
||||
*/
|
||||
override fun onPackageUpdateFinished(packageName: String?, uid: Int) {
|
||||
super.onPackageUpdateFinished(packageName, uid)
|
||||
if (packageName == this.packageName && UserHandle.getUserHandleForUid(uid) == user) {
|
||||
callback.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,11 @@
|
||||
|
||||
package com.android.systemui.controls.controller
|
||||
|
||||
import android.service.controls.actions.ControlAction
|
||||
import android.service.controls.IControlsActionCallback
|
||||
import android.service.controls.IControlsProvider
|
||||
import android.service.controls.IControlsSubscriber
|
||||
import android.service.controls.IControlsSubscription
|
||||
import android.service.controls.actions.ControlAction
|
||||
import android.service.controls.actions.ControlActionWrapper
|
||||
import android.util.Log
|
||||
|
||||
|
||||
@@ -41,11 +41,11 @@ import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@@ -378,7 +378,13 @@ class TestableControlsBindingControllerImpl(
|
||||
executor: DelayableExecutor,
|
||||
lazyController: Lazy<ControlsController>,
|
||||
userTracker: UserTracker
|
||||
) : ControlsBindingControllerImpl(context, executor, lazyController, userTracker) {
|
||||
) : ControlsBindingControllerImpl(
|
||||
context,
|
||||
executor,
|
||||
lazyController,
|
||||
mock(PackageUpdateMonitor.Factory::class.java),
|
||||
userTracker
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val providers = mutableListOf<ControlsProviderLifecycleManager>()
|
||||
|
||||
@@ -30,6 +30,10 @@ import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.argumentCaptor
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
@@ -39,17 +43,17 @@ import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.ArgumentMatchers.anyString
|
||||
import org.mockito.ArgumentMatchers.eq
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.anyInt
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.clearInvocations
|
||||
import org.mockito.Mockito.inOrder
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.verifyNoMoreInteractions
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@@ -62,16 +66,21 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
|
||||
private lateinit var subscriberService: IControlsSubscriber.Stub
|
||||
@Mock
|
||||
private lateinit var service: IControlsProvider.Stub
|
||||
|
||||
@Mock
|
||||
private lateinit var packageUpdateMonitor: PackageUpdateMonitor
|
||||
@Captor
|
||||
private lateinit var wrapperCaptor: ArgumentCaptor<ControlActionWrapper>
|
||||
|
||||
private lateinit var packageUpdateMonitorFactory: FakePackageUpdateMonitorFactory
|
||||
|
||||
private val componentName = ComponentName("test.pkg", "test.cls")
|
||||
private lateinit var manager: ControlsProviderLifecycleManager
|
||||
private lateinit var executor: FakeExecutor
|
||||
private lateinit var fakeSystemClock: FakeSystemClock
|
||||
|
||||
companion object {
|
||||
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
||||
private val USER = UserHandle.of(0)
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -79,16 +88,20 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
context.addMockService(componentName, service)
|
||||
executor = FakeExecutor(FakeSystemClock())
|
||||
fakeSystemClock = FakeSystemClock()
|
||||
executor = FakeExecutor(fakeSystemClock)
|
||||
`when`(service.asBinder()).thenCallRealMethod()
|
||||
`when`(service.queryLocalInterface(ArgumentMatchers.anyString())).thenReturn(service)
|
||||
`when`(service.queryLocalInterface(anyString())).thenReturn(service)
|
||||
|
||||
packageUpdateMonitorFactory = FakePackageUpdateMonitorFactory(packageUpdateMonitor)
|
||||
|
||||
manager = ControlsProviderLifecycleManager(
|
||||
context,
|
||||
executor,
|
||||
actionCallbackService,
|
||||
UserHandle.of(0),
|
||||
componentName
|
||||
USER,
|
||||
componentName,
|
||||
packageUpdateMonitorFactory,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -122,7 +135,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testNullBinding() {
|
||||
val mockContext = mock(Context::class.java)
|
||||
val mockContext = mock<Context>()
|
||||
lateinit var serviceConnection: ServiceConnection
|
||||
`when`(mockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer {
|
||||
val component = (it.arguments[0] as Intent).component
|
||||
@@ -139,8 +152,9 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
|
||||
mockContext,
|
||||
executor,
|
||||
actionCallbackService,
|
||||
UserHandle.of(0),
|
||||
componentName
|
||||
USER,
|
||||
componentName,
|
||||
packageUpdateMonitorFactory,
|
||||
)
|
||||
|
||||
nullManager.bindService()
|
||||
@@ -229,14 +243,15 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testFalseBindCallsUnbind() {
|
||||
val falseContext = mock(Context::class.java)
|
||||
val falseContext = mock<Context>()
|
||||
`when`(falseContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(false)
|
||||
val manager = ControlsProviderLifecycleManager(
|
||||
falseContext,
|
||||
executor,
|
||||
actionCallbackService,
|
||||
UserHandle.of(0),
|
||||
componentName
|
||||
USER,
|
||||
componentName,
|
||||
packageUpdateMonitorFactory,
|
||||
)
|
||||
manager.bindService()
|
||||
executor.runAllReady()
|
||||
@@ -247,4 +262,150 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
|
||||
verify(falseContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any())
|
||||
verify(falseContext).unbindService(captor.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPackageUpdateMonitor_createdWithCorrectValues() {
|
||||
assertEquals(USER, packageUpdateMonitorFactory.lastUser)
|
||||
assertEquals(componentName.packageName, packageUpdateMonitorFactory.lastPackage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBound_packageMonitorStartsMonitoring() {
|
||||
manager.bindService()
|
||||
executor.runAllReady()
|
||||
|
||||
// Service will connect and monitoring should start
|
||||
verify(packageUpdateMonitor).startMonitoring()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnPackageUpdateWhileBound_unbound_thenBindAgain() {
|
||||
val mockContext = mock<Context> {
|
||||
`when`(bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true)
|
||||
}
|
||||
|
||||
val manager = ControlsProviderLifecycleManager(
|
||||
mockContext,
|
||||
executor,
|
||||
actionCallbackService,
|
||||
USER,
|
||||
componentName,
|
||||
packageUpdateMonitorFactory,
|
||||
)
|
||||
|
||||
manager.bindService()
|
||||
executor.runAllReady()
|
||||
clearInvocations(mockContext)
|
||||
|
||||
packageUpdateMonitorFactory.lastCallback?.run()
|
||||
executor.runAllReady()
|
||||
|
||||
val inOrder = inOrder(mockContext)
|
||||
inOrder.verify(mockContext).unbindService(any())
|
||||
inOrder.verify(mockContext).bindServiceAsUser(any(), any(), anyInt(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnPackageUpdateWhenNotBound_nothingHappens() {
|
||||
val mockContext = mock<Context> {
|
||||
`when`(bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true)
|
||||
}
|
||||
|
||||
ControlsProviderLifecycleManager(
|
||||
mockContext,
|
||||
executor,
|
||||
actionCallbackService,
|
||||
USER,
|
||||
componentName,
|
||||
packageUpdateMonitorFactory,
|
||||
)
|
||||
|
||||
packageUpdateMonitorFactory.lastCallback?.run()
|
||||
verifyNoMoreInteractions(mockContext)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnbindService_stopsTracking() {
|
||||
manager.bindService()
|
||||
manager.unbindService()
|
||||
executor.runAllReady()
|
||||
|
||||
verify(packageUpdateMonitor).stopMonitoring()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRebindForPanelWithSameFlags() {
|
||||
val mockContext = mock<Context> {
|
||||
`when`(bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true)
|
||||
}
|
||||
|
||||
val manager = ControlsProviderLifecycleManager(
|
||||
mockContext,
|
||||
executor,
|
||||
actionCallbackService,
|
||||
USER,
|
||||
componentName,
|
||||
packageUpdateMonitorFactory,
|
||||
)
|
||||
|
||||
manager.bindServiceForPanel()
|
||||
executor.runAllReady()
|
||||
|
||||
val flagsCaptor = argumentCaptor<Int>()
|
||||
verify(mockContext).bindServiceAsUser(any(), any(), capture(flagsCaptor), any())
|
||||
clearInvocations(mockContext)
|
||||
|
||||
packageUpdateMonitorFactory.lastCallback?.run()
|
||||
executor.runAllReady()
|
||||
|
||||
verify(mockContext).bindServiceAsUser(any(), any(), eq(flagsCaptor.value), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBindAfterSecurityExceptionWorks() {
|
||||
val mockContext = mock<Context> {
|
||||
`when`(bindServiceAsUser(any(), any(), anyInt(), any()))
|
||||
.thenThrow(SecurityException("exception"))
|
||||
}
|
||||
|
||||
val manager = ControlsProviderLifecycleManager(
|
||||
mockContext,
|
||||
executor,
|
||||
actionCallbackService,
|
||||
USER,
|
||||
componentName,
|
||||
packageUpdateMonitorFactory,
|
||||
)
|
||||
|
||||
manager.bindServiceForPanel()
|
||||
executor.runAllReady()
|
||||
|
||||
`when`(mockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true)
|
||||
|
||||
manager.bindServiceForPanel()
|
||||
executor.runAllReady()
|
||||
|
||||
verify(mockContext, times(2)).bindServiceAsUser(any(), any(), anyInt(), any())
|
||||
}
|
||||
|
||||
private class FakePackageUpdateMonitorFactory(
|
||||
private val monitor: PackageUpdateMonitor
|
||||
) : PackageUpdateMonitor.Factory {
|
||||
|
||||
var lastUser: UserHandle? = null
|
||||
var lastPackage: String? = null
|
||||
var lastCallback: Runnable? = null
|
||||
|
||||
override fun create(
|
||||
user: UserHandle,
|
||||
packageName: String,
|
||||
callback: Runnable
|
||||
): PackageUpdateMonitor {
|
||||
lastUser = user
|
||||
lastPackage = packageName
|
||||
lastCallback = callback
|
||||
return monitor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.controls.controller
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.UserHandle
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.clearInvocations
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.verifyNoMoreInteractions
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class PackageUpdateMonitorTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var context: Context
|
||||
@Mock private lateinit var bgHandler: Handler
|
||||
|
||||
private lateinit var underTest: PackageUpdateMonitor
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startMonitoring_registerOnlyOnce() {
|
||||
underTest = PackageUpdateMonitor(USER, PACKAGE, {}, bgHandler, context)
|
||||
|
||||
underTest.startMonitoring()
|
||||
// There are two receivers registered
|
||||
verify(context, times(2))
|
||||
.registerReceiverAsUser(any(), eq(USER), any(), eq(null), eq(bgHandler))
|
||||
|
||||
underTest.startMonitoring()
|
||||
verifyNoMoreInteractions(context)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stopMonitoring_unregistersOnlyOnce() {
|
||||
underTest = PackageUpdateMonitor(USER, PACKAGE, {}, bgHandler, context)
|
||||
|
||||
underTest.startMonitoring()
|
||||
clearInvocations(context)
|
||||
|
||||
underTest.stopMonitoring()
|
||||
verify(context).unregisterReceiver(any())
|
||||
|
||||
underTest.stopMonitoring()
|
||||
verifyNoMoreInteractions(context)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPackageUpdated_correctPackageAndUser_callbackRuns() {
|
||||
val callback = mock<Runnable>()
|
||||
|
||||
underTest = PackageUpdateMonitor(USER, PACKAGE, callback, bgHandler, context)
|
||||
|
||||
underTest.onPackageUpdateFinished(PACKAGE, UserHandle.getUid(USER.identifier, 10000))
|
||||
verify(callback).run()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPackageUpdated_correctPackage_wrongUser_callbackDoesntRun() {
|
||||
val callback = mock<Runnable>()
|
||||
|
||||
underTest = PackageUpdateMonitor(USER, PACKAGE, callback, bgHandler, context)
|
||||
|
||||
underTest.onPackageUpdateFinished(PACKAGE, UserHandle.getUid(USER.identifier + 1, 10000))
|
||||
verify(callback, never()).run()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPackageUpdated_wrongPackage_correctUser_callbackDoesntRun() {
|
||||
val callback = mock<Runnable>()
|
||||
|
||||
underTest = PackageUpdateMonitor(USER, PACKAGE, callback, bgHandler, context)
|
||||
|
||||
underTest.onPackageUpdateFinished("bad", UserHandle.getUid(USER.identifier + 1, 10000))
|
||||
verify(callback, never()).run()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val USER = UserHandle.of(0)
|
||||
private val PACKAGE = "pkg"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user