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