Loading packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt +4 −2 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -93,7 +94,8 @@ open class ControlsBindingControllerImpl @Inject constructor( backgroundExecutor, actionCallbackService, currentUser, component component, packageUpdateMonitorFactory ) } Loading packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt +89 −48 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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]. Loading @@ -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 Loading @@ -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 Loading @@ -91,60 +95,56 @@ class ControlsProviderLifecycleManager( }) } private fun bindService(bind: Boolean, forPanel: Boolean = false) { 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 { 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") unbindAndCleanup("package updated") bindService(true, lastForPanel) } bindTryCount = 0 wrapper?.run { context.unbindService(serviceConnection) } wrapper = null } 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") } } } Loading @@ -159,14 +159,55 @@ class ControlsProviderLifecycleManager( } } override fun binderDied() { if (wrapper == null) return wrapper = null if (requiresBound) { @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) } } } else { unbindAndCleanup("unbind requested") packageUpdateMonitor.stopMonitoring() } } @WorkerThread private fun unbindAndCleanup(reason: String) { if (DEBUG) { Log.d(TAG, "binderDied") Log.d(TAG, "Unbinding service $intent. Reason: $reason") } wrapper = null try { doUnbind() } catch (e: IllegalArgumentException) { Log.e(TAG, "Failed to unbind service", e) } } // Try rebinding some time later @WorkerThread private fun doUnbind() { if (serviceConnection.connected.compareAndSet(true, false)) { context.unbindService(serviceConnection) } } Loading Loading @@ -313,7 +354,7 @@ class ControlsProviderLifecycleManager( fun run() { if (!callWrapper()) { queueServiceMethod(this) binderDied() executor.execute { unbindAndCleanup("couldn't call through binder") } } } Loading packages/SystemUI/src/com/android/systemui/controls/controller/PackageUpdateMonitor.kt 0 → 100644 +76 −0 Original line number Diff line number Diff line /* * 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() } } } packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt +8 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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>() Loading Loading
packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt +4 −2 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -93,7 +94,8 @@ open class ControlsBindingControllerImpl @Inject constructor( backgroundExecutor, actionCallbackService, currentUser, component component, packageUpdateMonitorFactory ) } Loading
packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt +89 −48 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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]. Loading @@ -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 Loading @@ -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 Loading @@ -91,60 +95,56 @@ class ControlsProviderLifecycleManager( }) } private fun bindService(bind: Boolean, forPanel: Boolean = false) { 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 { 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") unbindAndCleanup("package updated") bindService(true, lastForPanel) } bindTryCount = 0 wrapper?.run { context.unbindService(serviceConnection) } wrapper = null } 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") } } } Loading @@ -159,14 +159,55 @@ class ControlsProviderLifecycleManager( } } override fun binderDied() { if (wrapper == null) return wrapper = null if (requiresBound) { @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) } } } else { unbindAndCleanup("unbind requested") packageUpdateMonitor.stopMonitoring() } } @WorkerThread private fun unbindAndCleanup(reason: String) { if (DEBUG) { Log.d(TAG, "binderDied") Log.d(TAG, "Unbinding service $intent. Reason: $reason") } wrapper = null try { doUnbind() } catch (e: IllegalArgumentException) { Log.e(TAG, "Failed to unbind service", e) } } // Try rebinding some time later @WorkerThread private fun doUnbind() { if (serviceConnection.connected.compareAndSet(true, false)) { context.unbindService(serviceConnection) } } Loading Loading @@ -313,7 +354,7 @@ class ControlsProviderLifecycleManager( fun run() { if (!callWrapper()) { queueServiceMethod(this) binderDied() executor.execute { unbindAndCleanup("couldn't call through binder") } } } Loading
packages/SystemUI/src/com/android/systemui/controls/controller/PackageUpdateMonitor.kt 0 → 100644 +76 −0 Original line number Diff line number Diff line /* * 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() } } }
packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt +8 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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>() Loading