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