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