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.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 = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) 132 133 val controlsController = controlsComponent.getControlsController().get() 134 val componentsToSeed = mutableListOf<ComponentName>() 135 var i = 0 136 while (i < Math.min(SEEDING_MAX, preferredControlsPackages.size)) { 137 val pkg = preferredControlsPackages[i] 138 serviceInfos.forEach { 139 if (pkg.equals(it.componentName.packageName) && !seededPackages.contains(pkg)) { 140 if (controlsController.countFavoritesForComponent(it.componentName) > 0) { 141 // When there are existing controls but no saved preference, assume it 142 // is out of sync, perhaps through a device restore, and update the 143 // preference 144 addPackageToSeededSet(prefs, pkg) 145 } else if (it.panelActivity != null) { 146 // Do not seed for packages with panels 147 addPackageToSeededSet(prefs, pkg) 148 } else { 149 componentsToSeed.add(it.componentName) 150 } 151 } 152 } 153 i++ 154 } 155 156 if (componentsToSeed.isEmpty()) return 157 158 controlsController.seedFavoritesForComponents( 159 componentsToSeed, 160 { response -> 161 Log.d(TAG, "Controls seeded: $response") 162 if (response.accepted) { 163 addPackageToSeededSet(prefs, response.packageName) 164 if (position == null) { 165 position = QS_DEFAULT_POSITION 166 } 167 fireControlsUpdate() 168 169 controlsComponent.getControlsListingController().ifPresent { 170 it.removeCallback(listingCallback) 171 } 172 } 173 }) 174 } 175 176 private fun addPackageToSeededSet(prefs: SharedPreferences, pkg: String) { 177 val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) 178 val updatedPkgs = seededPackages.toMutableSet() 179 updatedPkgs.add(pkg) 180 prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply() 181 } 182 } 183