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