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