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