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

Commit b4dbab65 authored by Fabian Kozynski's avatar Fabian Kozynski Committed by Android (Google) Code Review
Browse files

Merge "Rebind to ControlsProviderService if package updates" into udc-dev

parents 323691f2 42e68e10
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -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
        )
    }

+89 −48
Original line number Diff line number Diff line
@@ -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) {
    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")
            }
        }
    }

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

@@ -313,7 +354,7 @@ class ControlsProviderLifecycleManager(
        fun run() {
            if (!callWrapper()) {
                queueServiceMethod(this)
                binderDied()
                executor.execute { unbindAndCleanup("couldn't call through binder") }
            }
        }

+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()
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -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

+8 −2
Original line number Diff line number Diff line
@@ -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>()
Loading