1 /* 2 * Copyright 2019 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.settings.development.graphicsdriver; 18 19 import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.UPDATABLE_DRIVER_DEFAULT; 20 import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.UPDATABLE_DRIVER_OFF; 21 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.PackageManager; 26 import android.content.res.Resources; 27 import android.os.Handler; 28 import android.os.Looper; 29 import android.provider.Settings; 30 31 import androidx.annotation.VisibleForTesting; 32 import androidx.preference.ListPreference; 33 import androidx.preference.Preference; 34 import androidx.preference.PreferenceGroup; 35 import androidx.preference.PreferenceScreen; 36 37 import com.android.settings.R; 38 import com.android.settings.core.BasePreferenceController; 39 import com.android.settingslib.core.lifecycle.LifecycleObserver; 40 import com.android.settingslib.core.lifecycle.events.OnStart; 41 import com.android.settingslib.core.lifecycle.events.OnStop; 42 import com.android.settingslib.development.DevelopmentSettingsEnabler; 43 44 import java.text.Collator; 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.Collections; 48 import java.util.Comparator; 49 import java.util.HashSet; 50 import java.util.List; 51 import java.util.Set; 52 53 /** 54 * Controller of all the per App based list preferences. 55 */ 56 public class GraphicsDriverAppPreferenceController extends BasePreferenceController 57 implements Preference.OnPreferenceChangeListener, 58 GraphicsDriverContentObserver.OnGraphicsDriverContentChangedListener, LifecycleObserver, 59 OnStart, OnStop { 60 61 private final Context mContext; 62 private final ContentResolver mContentResolver; 63 private final String mPreferenceTitle; 64 private final String mPreferenceDefault; 65 private final String mPreferenceProductionDriver; 66 private final String mPreferencePrereleaseDriver; 67 private final String mPreferenceSystem; 68 @VisibleForTesting 69 CharSequence[] mEntryList; 70 @VisibleForTesting 71 GraphicsDriverContentObserver mGraphicsDriverContentObserver; 72 73 private final List<AppInfo> mAppInfos; 74 private final Set<String> mDevOptInApps; 75 private final Set<String> mDevPrereleaseOptInApps; 76 private final Set<String> mDevOptOutApps; 77 78 private PreferenceGroup mPreferenceGroup; 79 GraphicsDriverAppPreferenceController(Context context, String key)80 public GraphicsDriverAppPreferenceController(Context context, String key) { 81 super(context, key); 82 83 mContext = context; 84 mContentResolver = context.getContentResolver(); 85 mGraphicsDriverContentObserver = 86 new GraphicsDriverContentObserver(new Handler(Looper.getMainLooper()), this); 87 88 final Resources resources = context.getResources(); 89 mPreferenceTitle = resources.getString(R.string.graphics_driver_app_preference_title); 90 mPreferenceDefault = resources.getString(R.string.graphics_driver_app_preference_default); 91 mPreferenceProductionDriver = 92 resources.getString(R.string.graphics_driver_app_preference_production_driver); 93 mPreferencePrereleaseDriver = 94 resources.getString(R.string.graphics_driver_app_preference_prerelease_driver); 95 mPreferenceSystem = resources.getString(R.string.graphics_driver_app_preference_system); 96 mEntryList = GraphicsDriverEnableForAllAppsPreferenceController.constructEntryList( 97 mContext, true); 98 99 // TODO: Move this task to background if there's potential ANR/Jank. 100 // Update the UI when all the app infos are ready. 101 mAppInfos = getAppInfos(context); 102 103 mDevOptInApps = 104 getGlobalSettingsString(mContentResolver, 105 Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS); 106 mDevPrereleaseOptInApps = getGlobalSettingsString( 107 mContentResolver, Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS); 108 mDevOptOutApps = 109 getGlobalSettingsString(mContentResolver, 110 Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS); 111 } 112 113 @Override getAvailabilityStatus()114 public int getAvailabilityStatus() { 115 return DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext) 116 && (Settings.Global.getInt(mContentResolver, 117 Settings.Global.UPDATABLE_DRIVER_ALL_APPS, UPDATABLE_DRIVER_DEFAULT) 118 != UPDATABLE_DRIVER_OFF) 119 ? AVAILABLE 120 : CONDITIONALLY_UNAVAILABLE; 121 } 122 123 @Override displayPreference(PreferenceScreen screen)124 public void displayPreference(PreferenceScreen screen) { 125 super.displayPreference(screen); 126 mPreferenceGroup = screen.findPreference(getPreferenceKey()); 127 128 final Context context = mPreferenceGroup.getContext(); 129 for (AppInfo appInfo : mAppInfos) { 130 mPreferenceGroup.addPreference( 131 createListPreference(context, appInfo.info.packageName, appInfo.label)); 132 } 133 } 134 135 @Override onStart()136 public void onStart() { 137 mGraphicsDriverContentObserver.register(mContentResolver); 138 } 139 140 @Override onStop()141 public void onStop() { 142 mGraphicsDriverContentObserver.unregister(mContentResolver); 143 } 144 145 @Override updateState(Preference preference)146 public void updateState(Preference preference) { 147 preference.setVisible(isAvailable()); 148 } 149 150 @Override onPreferenceChange(Preference preference, Object newValue)151 public boolean onPreferenceChange(Preference preference, Object newValue) { 152 final ListPreference listPref = (ListPreference) preference; 153 final String value = newValue.toString(); 154 final String packageName = preference.getKey(); 155 156 // When user choose a new preference, update both Sets for 157 // opt-in and opt-out apps. Then set the new summary text. 158 if (value.equals(mPreferenceSystem)) { 159 mDevOptInApps.remove(packageName); 160 mDevPrereleaseOptInApps.remove(packageName); 161 mDevOptOutApps.add(packageName); 162 } else if (value.equals(mPreferenceProductionDriver)) { 163 mDevOptInApps.add(packageName); 164 mDevPrereleaseOptInApps.remove(packageName); 165 mDevOptOutApps.remove(packageName); 166 } else if (value.equals(mPreferencePrereleaseDriver)) { 167 mDevOptInApps.remove(packageName); 168 mDevPrereleaseOptInApps.add(packageName); 169 mDevOptOutApps.remove(packageName); 170 } else { 171 mDevOptInApps.remove(packageName); 172 mDevPrereleaseOptInApps.remove(packageName); 173 mDevOptOutApps.remove(packageName); 174 } 175 listPref.setValue(value); 176 listPref.setSummary(value); 177 178 // Push the updated Sets for stable/prerelease opt-in and opt-out apps to 179 // corresponding Settings.Global.UPDATABLE_DRIVER_[PRODUCTION|PRERELEASE]_OPT_(IN|OUT)_APPS 180 Settings.Global.putString(mContentResolver, 181 Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS, 182 String.join(",", mDevOptInApps)); 183 Settings.Global.putString(mContentResolver, 184 Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS, 185 String.join(",", mDevPrereleaseOptInApps)); 186 Settings.Global.putString(mContentResolver, 187 Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS, 188 String.join(",", mDevOptOutApps)); 189 190 return true; 191 } 192 193 @Override onGraphicsDriverContentChanged()194 public void onGraphicsDriverContentChanged() { 195 updateState(mPreferenceGroup); 196 } 197 198 // AppInfo class to achieve loading the application label only once 199 class AppInfo { AppInfo(PackageManager packageManager, ApplicationInfo applicationInfo)200 AppInfo(PackageManager packageManager, ApplicationInfo applicationInfo) { 201 info = applicationInfo; 202 label = packageManager.getApplicationLabel(applicationInfo).toString(); 203 } 204 205 public final ApplicationInfo info; 206 public final String label; 207 } 208 209 // List of non-system packages that are installed for the current user. getAppInfos(Context context)210 private List<AppInfo> getAppInfos(Context context) { 211 final PackageManager packageManager = context.getPackageManager(); 212 final List<ApplicationInfo> applicationInfos = 213 packageManager.getInstalledApplications(0 /* flags */); 214 215 final List<AppInfo> appInfos = new ArrayList<>(); 216 for (ApplicationInfo applicationInfo : applicationInfos) { 217 if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { 218 appInfos.add(new AppInfo(packageManager, applicationInfo)); 219 } 220 } 221 222 Collections.sort(appInfos, mAppInfoComparator); 223 224 return appInfos; 225 } 226 227 // Parse the raw comma separated package names into a String Set getGlobalSettingsString(ContentResolver contentResolver, String name)228 private Set<String> getGlobalSettingsString(ContentResolver contentResolver, String name) { 229 final String settingsValue = Settings.Global.getString(contentResolver, name); 230 if (settingsValue == null) { 231 return new HashSet<>(); 232 } 233 234 final Set<String> valueSet = new HashSet<>(Arrays.asList(settingsValue.split(","))); 235 valueSet.remove(""); 236 237 return valueSet; 238 } 239 240 private final Comparator<AppInfo> mAppInfoComparator = new Comparator<AppInfo>() { 241 public int compare(AppInfo a, AppInfo b) { 242 return Collator.getInstance().compare(a.label, b.label); 243 } 244 }; 245 246 @VisibleForTesting createListPreference( Context context, String packageName, String appName)247 protected ListPreference createListPreference( 248 Context context, String packageName, String appName) { 249 final ListPreference listPreference = new ListPreference(context); 250 251 listPreference.setKey(packageName); 252 listPreference.setTitle(appName); 253 listPreference.setDialogTitle(mPreferenceTitle); 254 listPreference.setEntries(mEntryList); 255 listPreference.setEntryValues(mEntryList); 256 257 // Initialize preference default and summary with the opt in/out choices 258 // from Settings.Global.UPDATABLE_DRIVER_[PRODUCTION|PRERELEASE]_OPT_[IN|OUT]_APPS 259 if (mDevOptOutApps.contains(packageName)) { 260 listPreference.setValue(mPreferenceSystem); 261 listPreference.setSummary(mPreferenceSystem); 262 } else if (mDevPrereleaseOptInApps.contains(packageName)) { 263 listPreference.setValue(mPreferencePrereleaseDriver); 264 listPreference.setSummary(mPreferencePrereleaseDriver); 265 } else if (mDevOptInApps.contains(packageName)) { 266 listPreference.setValue(mPreferenceProductionDriver); 267 listPreference.setSummary(mPreferenceProductionDriver); 268 } else { 269 listPreference.setValue(mPreferenceDefault); 270 listPreference.setSummary(mPreferenceDefault); 271 } 272 273 listPreference.setOnPreferenceChangeListener(this); 274 275 return listPreference; 276 } 277 } 278