1 /* 2 * 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.controls.controller.ControlsController 30 import com.android.systemui.controls.dagger.ControlsComponent 31 import com.android.systemui.controls.management.ControlsListingController 32 import com.android.systemui.controls.panels.AuthorizedPanelsRepository 33 import com.android.systemui.controls.panels.SelectedComponentRepository 34 import com.android.systemui.controls.ui.SelectedItem 35 import com.android.systemui.dagger.SysUISingleton 36 import com.android.systemui.dagger.qualifiers.Background 37 import com.android.systemui.settings.UserTracker 38 import java.util.concurrent.Executor 39 import javax.inject.Inject 40 41 /** 42 * Started with SystemUI to perform early operations for device controls subsystem (only if enabled) 43 * 44 * In particular, it will perform the following: 45 * * If there is no preferred selection for provider and at least one of the preferred packages 46 * provides a panel, it will select the first one that does. 47 * * If the preferred selection provides a panel, it will bind to that service (to reduce latency on 48 * displaying the panel). 49 * 50 * It will also perform those operations on user change. 51 */ 52 @SysUISingleton 53 class ControlsStartable 54 @Inject 55 constructor( 56 @Background private val executor: Executor, 57 private val controlsComponent: ControlsComponent, 58 private val userTracker: UserTracker, 59 private val authorizedPanelsRepository: AuthorizedPanelsRepository, 60 private val selectedComponentRepository: SelectedComponentRepository, 61 private val userManager: UserManager, 62 private val broadcastDispatcher: BroadcastDispatcher, 63 ) : CoreStartable { 64 65 // These two controllers can only be accessed after `start` method once we've checked if the 66 // feature is enabled 67 private val controlsController: ControlsController 68 get() = controlsComponent.getControlsController().get() 69 70 private val controlsListingController: ControlsListingController 71 get() = controlsComponent.getControlsListingController().get() 72 73 private val userTrackerCallback = 74 object : UserTracker.Callback { 75 override fun onUserChanged(newUser: Int, userContext: Context) { 76 controlsController.changeUser(UserHandle.of(newUser)) 77 startForUser() 78 } 79 } 80 81 override fun start() {} 82 83 override fun onBootCompleted() { 84 if (!controlsComponent.isEnabled()) { 85 // Controls is disabled, we don't need this anymore 86 return 87 } 88 executor.execute(this::startForUser) 89 userTracker.addCallback(userTrackerCallback, executor) 90 } 91 92 @WorkerThread 93 private fun startForUser() { 94 controlsListingController.forceReload() 95 selectDefaultPanelIfNecessary() 96 bindToPanel() 97 } 98 99 private fun selectDefaultPanelIfNecessary() { 100 if (!selectedComponentRepository.shouldAddDefaultComponent()) { 101 return 102 } 103 val currentSelection = controlsController.getPreferredSelection() 104 if (currentSelection == SelectedItem.EMPTY_SELECTION) { 105 val availableServices = controlsListingController.getCurrentServices() 106 val panels = availableServices.filter { it.panelActivity != null } 107 authorizedPanelsRepository 108 .getPreferredPackages() 109 // Looking for the first element in the string array such that there is one package 110 // that has a panel. It will return null if there are no packages in the array, 111 // or if no packages in the array have a panel associated with it. 112 .firstNotNullOfOrNull { name -> 113 panels.firstOrNull { it.componentName.packageName == name } 114 } 115 ?.let { info -> 116 controlsController.setPreferredSelection( 117 SelectedItem.PanelItem(info.loadLabel(), info.componentName) 118 ) 119 } 120 } 121 } 122 123 private fun bindToPanel() { 124 if (userManager.isUserUnlocked(userTracker.userId)) { 125 bindToPanelInternal() 126 } else { 127 broadcastDispatcher.registerReceiver( 128 receiver = object : BroadcastReceiver() { 129 override fun onReceive(context: Context?, intent: Intent?) { 130 if (userManager.isUserUnlocked(userTracker.userId)) { 131 bindToPanelInternal() 132 broadcastDispatcher.unregisterReceiver(this) 133 } 134 } 135 }, 136 filter = IntentFilter(Intent.ACTION_USER_UNLOCKED), 137 executor = executor, 138 user = userTracker.userHandle, 139 ) 140 } 141 } 142 143 private fun bindToPanelInternal() { 144 val currentSelection = controlsController.getPreferredSelection() 145 val panels = 146 controlsListingController.getCurrentServices().filter { it.panelActivity != null } 147 if (currentSelection is SelectedItem.PanelItem && 148 panels.firstOrNull { it.componentName == currentSelection.componentName } != null 149 ) { 150 controlsController.bindComponentForPanel(currentSelection.componentName) 151 } 152 } 153 } 154