1 /* <lambda>null2 * Copyright (C) 2021 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 package com.android.systemui.statusbar.policy 18 19 import android.content.ComponentName 20 import android.content.Context 21 import android.content.SharedPreferences 22 import android.provider.Settings 23 import android.util.Log 24 import com.android.systemui.controls.ControlsServiceInfo 25 import com.android.systemui.controls.dagger.ControlsComponent 26 import com.android.systemui.controls.management.ControlsListingController 27 import com.android.systemui.dagger.SysUISingleton 28 import com.android.systemui.res.R 29 import com.android.systemui.settings.UserContextProvider 30 import com.android.systemui.statusbar.policy.DeviceControlsController.Callback 31 import com.android.systemui.util.settings.SecureSettings 32 import javax.inject.Inject 33 34 /** 35 * Watches for Device Controls QS Tile activation, which can happen in two ways: 36 * <ol> 37 * <li>Migration from Power Menu - For existing Android 11 users, create a tile in a high priority 38 * position. 39 * <li>Device controls service becomes available - For non-migrated users, create a tile and place 40 * at the end of active tiles, and initiate seeding where possible. 41 * </ol> 42 */ 43 @SysUISingleton 44 public class DeviceControlsControllerImpl 45 @Inject 46 constructor( 47 private val context: Context, 48 private val controlsComponent: ControlsComponent, 49 private val userContextProvider: UserContextProvider, 50 private val secureSettings: SecureSettings 51 ) : DeviceControlsController { 52 53 private var callback: Callback? = null 54 internal var position: Int? = null 55 56 private val listingCallback = 57 object : ControlsListingController.ControlsListingCallback { 58 override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { 59 if (!serviceInfos.isEmpty()) { 60 seedFavorites(serviceInfos) 61 } 62 } 63 } 64 65 companion object { 66 private const val TAG = "DeviceControlsControllerImpl" 67 internal const val QS_PRIORITY_POSITION = 3 68 internal const val QS_DEFAULT_POSITION = 7 69 70 internal const val PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted" 71 const val PREFS_CONTROLS_FILE = "controls_prefs" 72 private const val SEEDING_MAX = 2 73 } 74 75 private fun checkMigrationToQs() { 76 controlsComponent.getControlsController().ifPresent { 77 if (!it.getFavorites().isEmpty()) { 78 position = QS_PRIORITY_POSITION 79 fireControlsUpdate() 80 } 81 } 82 } 83 84 /** 85 * This migration logic assumes that something like [AutoAddTracker] is tracking state 86 * externally, and won't call this method after receiving a response via 87 * [Callback#onControlsUpdate], once per user. Otherwise the calculated position may be 88 * incorrect. 89 */ 90 override fun setCallback(callback: Callback) { 91 if (!controlsComponent.isEnabled()) { 92 callback.removeControlsAutoTracker() 93 return 94 } 95 // Treat any additional call as a reset before recalculating 96 removeCallback() 97 this.callback = callback 98 99 if (secureSettings.getInt(Settings.Secure.CONTROLS_ENABLED, 1) == 0) { 100 fireControlsUpdate() 101 } else { 102 checkMigrationToQs() 103 controlsComponent.getControlsListingController().ifPresent { 104 it.addCallback(listingCallback) 105 } 106 } 107 } 108 109 override fun removeCallback() { 110 position = null 111 callback = null 112 controlsComponent.getControlsListingController().ifPresent { 113 it.removeCallback(listingCallback) 114 } 115 } 116 117 private fun fireControlsUpdate() { 118 Log.i(TAG, "Setting DeviceControlsTile position: $position") 119 callback?.onControlsUpdate(position) 120 } 121 122 /** 123 * See if any available control service providers match one of the preferred components. If they 124 * do, and there are no current favorites for that component, query the preferred component for 125 * a limited number of suggested controls. 126 */ 127 private fun seedFavorites(serviceInfos: List<ControlsServiceInfo>) { 128 val preferredControlsPackages = 129 context.getResources().getStringArray(R.array.config_controlsPreferredPackages) 130 131 val prefs = 132 userContextProvider.userContext.getSharedPreferences( 133 PREFS_CONTROLS_FILE, 134 Context.MODE_PRIVATE 135 ) 136 val seededPackages = 137 prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet() 138 139 val controlsController = controlsComponent.getControlsController().get() 140 val componentsToSeed = mutableListOf<ComponentName>() 141 var i = 0 142 while (i < Math.min(SEEDING_MAX, preferredControlsPackages.size)) { 143 val pkg = preferredControlsPackages[i] 144 serviceInfos.forEach { 145 if (pkg.equals(it.componentName.packageName) && !seededPackages.contains(pkg)) { 146 if (controlsController.countFavoritesForComponent(it.componentName) > 0) { 147 // When there are existing controls but no saved preference, assume it 148 // is out of sync, perhaps through a device restore, and update the 149 // preference 150 addPackageToSeededSet(prefs, pkg) 151 } else if (it.panelActivity != null) { 152 // Do not seed for packages with panels 153 addPackageToSeededSet(prefs, pkg) 154 } else { 155 componentsToSeed.add(it.componentName) 156 } 157 } 158 } 159 i++ 160 } 161 162 if (componentsToSeed.isEmpty()) return 163 164 controlsController.seedFavoritesForComponents( 165 componentsToSeed, 166 { response -> 167 Log.d(TAG, "Controls seeded: $response") 168 if (response.accepted) { 169 addPackageToSeededSet(prefs, response.packageName) 170 if (position == null) { 171 position = QS_DEFAULT_POSITION 172 } 173 fireControlsUpdate() 174 175 controlsComponent.getControlsListingController().ifPresent { 176 it.removeCallback(listingCallback) 177 } 178 } 179 } 180 ) 181 } 182 183 private fun addPackageToSeededSet(prefs: SharedPreferences, pkg: String) { 184 val seededPackages = 185 prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet() 186 val updatedPkgs = seededPackages.toMutableSet() 187 updatedPkgs.add(pkg) 188 prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply() 189 } 190 } 191