1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 18 package com.android.systemui.controls.start 19 20 import android.content.BroadcastReceiver 21 import android.content.Context 22 import android.content.Intent 23 import android.content.IntentFilter 24 import android.os.UserHandle 25 import android.os.UserManager 26 import androidx.annotation.WorkerThread 27 import com.android.systemui.CoreStartable 28 import com.android.systemui.broadcast.BroadcastDispatcher 29 import com.android.systemui.common.shared.model.PackageChangeModel 30 import com.android.systemui.common.domain.interactor.PackageChangeInteractor 31 import com.android.systemui.controls.controller.ControlsController 32 import com.android.systemui.controls.dagger.ControlsComponent 33 import com.android.systemui.controls.management.ControlsListingController 34 import com.android.systemui.controls.panels.AuthorizedPanelsRepository 35 import com.android.systemui.controls.panels.SelectedComponentRepository 36 import com.android.systemui.controls.ui.SelectedItem 37 import com.android.systemui.dagger.SysUISingleton 38 import com.android.systemui.dagger.qualifiers.Application 39 import com.android.systemui.dagger.qualifiers.Background 40 import com.android.systemui.settings.UserTracker 41 import kotlinx.coroutines.CoroutineDispatcher 42 import kotlinx.coroutines.CoroutineScope 43 import kotlinx.coroutines.Job 44 import kotlinx.coroutines.flow.filter 45 import kotlinx.coroutines.flow.filterIsInstance 46 import kotlinx.coroutines.flow.flowOn 47 import kotlinx.coroutines.flow.launchIn 48 import kotlinx.coroutines.flow.onEach 49 import java.util.concurrent.Executor 50 import javax.inject.Inject 51 52 /** 53 * Started with SystemUI to perform early operations for device controls subsystem (only if enabled) 54 * 55 * In particular, it will perform the following: 56 * * If there is no preferred selection for provider and at least one of the preferred packages 57 * provides a panel, it will select the first one that does. 58 * * If the preferred selection provides a panel, it will bind to that service (to reduce latency on 59 * displaying the panel). 60 * 61 * It will also perform those operations on user change. 62 */ 63 @SysUISingleton 64 class ControlsStartable 65 @Inject 66 constructor( 67 @Application private val scope: CoroutineScope, 68 @Background private val bgDispatcher: CoroutineDispatcher, 69 @Background private val executor: Executor, 70 private val controlsComponent: ControlsComponent, 71 private val userTracker: UserTracker, 72 private val authorizedPanelsRepository: AuthorizedPanelsRepository, 73 private val selectedComponentRepository: SelectedComponentRepository, 74 private val packageChangeInteractor: PackageChangeInteractor, 75 private val userManager: UserManager, 76 private val broadcastDispatcher: BroadcastDispatcher, 77 ) : CoreStartable { 78 79 // These two controllers can only be accessed after `start` method once we've checked if the 80 // feature is enabled 81 private val controlsController: ControlsController 82 get() = controlsComponent.getControlsController().get() 83 84 private val controlsListingController: ControlsListingController 85 get() = controlsComponent.getControlsListingController().get() 86 87 private val userTrackerCallback = 88 object : UserTracker.Callback { 89 override fun onUserChanged(newUser: Int, userContext: Context) { 90 controlsController.changeUser(UserHandle.of(newUser)) 91 startForUser() 92 } 93 } 94 95 private var packageJob: Job? = null 96 97 override fun start() {} 98 99 override fun onBootCompleted() { 100 if (!controlsComponent.isEnabled()) { 101 // Controls is disabled, we don't need this anymore 102 return 103 } 104 executor.execute(this::startForUser) 105 userTracker.addCallback(userTrackerCallback, executor) 106 } 107 108 @WorkerThread 109 private fun startForUser() { 110 controlsListingController.forceReload() 111 selectDefaultPanelIfNecessary() 112 bindToPanel() 113 monitorPackageUninstall() 114 } 115 116 private fun monitorPackageUninstall() { 117 packageJob?.cancel() 118 packageJob = packageChangeInteractor.packageChanged(userTracker.userHandle) 119 .filterIsInstance<PackageChangeModel.Uninstalled>() 120 .filter { 121 val selectedPackage = 122 selectedComponentRepository.getSelectedComponent()?.componentName?.packageName 123 // Selected package was uninstalled 124 it.packageName == selectedPackage 125 } 126 .onEach { selectedComponentRepository.removeSelectedComponent() } 127 .flowOn(bgDispatcher) 128 .launchIn(scope) 129 } 130 131 private fun selectDefaultPanelIfNecessary() { 132 if (!selectedComponentRepository.shouldAddDefaultComponent()) { 133 return 134 } 135 val currentSelection = controlsController.getPreferredSelection() 136 if (currentSelection == SelectedItem.EMPTY_SELECTION) { 137 val availableServices = controlsListingController.getCurrentServices() 138 val panels = availableServices.filter { it.panelActivity != null } 139 authorizedPanelsRepository 140 .getPreferredPackages() 141 // Looking for the first element in the string array such that there is one package 142 // that has a panel. It will return null if there are no packages in the array, 143 // or if no packages in the array have a panel associated with it. 144 .firstNotNullOfOrNull { name -> 145 panels.firstOrNull { it.componentName.packageName == name } 146 } 147 ?.let { info -> 148 controlsController.setPreferredSelection( 149 SelectedItem.PanelItem(info.loadLabel(), info.componentName) 150 ) 151 } 152 } 153 } 154 155 private fun bindToPanel() { 156 if (userManager.isUserUnlocked(userTracker.userId)) { 157 bindToPanelInternal() 158 } else { 159 broadcastDispatcher.registerReceiver( 160 receiver = object : BroadcastReceiver() { 161 override fun onReceive(context: Context?, intent: Intent?) { 162 if (userManager.isUserUnlocked(userTracker.userId)) { 163 bindToPanelInternal() 164 broadcastDispatcher.unregisterReceiver(this) 165 } 166 } 167 }, 168 filter = IntentFilter(Intent.ACTION_USER_UNLOCKED), 169 executor = executor, 170 user = userTracker.userHandle, 171 ) 172 } 173 } 174 175 private fun bindToPanelInternal() { 176 val currentSelection = controlsController.getPreferredSelection() 177 val panels = 178 controlsListingController.getCurrentServices().filter { it.panelActivity != null } 179 if (currentSelection is SelectedItem.PanelItem && 180 panels.firstOrNull { it.componentName == currentSelection.componentName } != null 181 ) { 182 controlsController.bindComponentForPanel(currentSelection.componentName) 183 } 184 } 185 } 186