• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.compat;
18 
19 import static com.android.internal.compat.OverrideAllowedState.ALLOWED;
20 
21 import android.app.settings.SettingsEnums;
22 import android.compat.Compatibility.ChangeConfig;
23 import android.content.Context;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.PackageManager;
26 import android.graphics.drawable.Drawable;
27 import android.os.Bundle;
28 import android.os.RemoteException;
29 import android.os.ServiceManager;
30 import android.util.ArraySet;
31 
32 import androidx.annotation.VisibleForTesting;
33 import androidx.preference.Preference;
34 import androidx.preference.Preference.OnPreferenceChangeListener;
35 import androidx.preference.PreferenceCategory;
36 import androidx.preference.SwitchPreference;
37 
38 import com.android.internal.compat.CompatibilityChangeConfig;
39 import com.android.internal.compat.CompatibilityChangeInfo;
40 import com.android.internal.compat.IPlatformCompat;
41 import com.android.settings.R;
42 import com.android.settings.dashboard.DashboardFragment;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.TreeMap;
48 
49 /**
50  * Dashboard for Platform Compat preferences.
51  */
52 public class PlatformCompatDashboard extends DashboardFragment {
53     private static final String TAG = "PlatformCompatDashboard";
54     public static final String COMPAT_APP = "compat_app";
55 
56     private IPlatformCompat mPlatformCompat;
57 
58     private CompatibilityChangeInfo[] mChanges;
59 
60     @VisibleForTesting
61     String mSelectedApp;
62 
63     @Override
getMetricsCategory()64     public int getMetricsCategory() {
65         return SettingsEnums.SETTINGS_PLATFORM_COMPAT_DASHBOARD;
66     }
67 
68     @Override
getLogTag()69     protected String getLogTag() {
70         return TAG;
71     }
72 
73     @Override
getPreferenceScreenResId()74     protected int getPreferenceScreenResId() {
75         return R.xml.platform_compat_settings;
76     }
77 
78     @Override
getHelpResource()79     public int getHelpResource() {
80         return 0;
81     }
82 
getPlatformCompat()83     IPlatformCompat getPlatformCompat() {
84         if (mPlatformCompat == null) {
85             mPlatformCompat = IPlatformCompat.Stub
86                     .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
87         }
88         return mPlatformCompat;
89     }
90 
91     @Override
onCreate(Bundle icicle)92     public void onCreate(Bundle icicle) {
93         super.onCreate(icicle);
94         try {
95             mChanges = getPlatformCompat().listUIChanges();
96         } catch (RemoteException e) {
97             throw new RuntimeException("Could not list changes!", e);
98         }
99     }
100 
101     @Override
onResume()102     public void onResume() {
103         super.onResume();
104         if (isFinishingOrDestroyed()) {
105             return;
106         }
107         Bundle arguments = getArguments();
108         if (arguments == null) {
109             finish();
110             return;
111         }
112         mSelectedApp = arguments.getString(COMPAT_APP);
113         try {
114             final ApplicationInfo applicationInfo = getApplicationInfo();
115             addPreferences(applicationInfo);
116         } catch (PackageManager.NameNotFoundException ignored) {
117             finish();
118         }
119     }
120 
addPreferences(ApplicationInfo applicationInfo)121     private void addPreferences(ApplicationInfo applicationInfo) {
122         getPreferenceScreen().removeAll();
123         getPreferenceScreen().addPreference(createAppPreference(applicationInfo));
124         // Differentiate compatibility changes into default enabled, default disabled and enabled
125         // after target sdk.
126         final CompatibilityChangeConfig configMappings = getAppChangeMappings();
127         final List<CompatibilityChangeInfo> enabledChanges = new ArrayList<>();
128         final List<CompatibilityChangeInfo> disabledChanges = new ArrayList<>();
129         final Map<Integer, List<CompatibilityChangeInfo>> targetSdkChanges = new TreeMap<>();
130         for (CompatibilityChangeInfo change : mChanges) {
131             if (change.getEnableSinceTargetSdk() > 0) {
132                 List<CompatibilityChangeInfo> sdkChanges;
133                 if (!targetSdkChanges.containsKey(change.getEnableSinceTargetSdk())) {
134                     sdkChanges = new ArrayList<>();
135                     targetSdkChanges.put(change.getEnableSinceTargetSdk(), sdkChanges);
136                 } else {
137                     sdkChanges = targetSdkChanges.get(change.getEnableSinceTargetSdk());
138                 }
139                 sdkChanges.add(change);
140             } else if (change.getDisabled()) {
141                 disabledChanges.add(change);
142             } else {
143                 enabledChanges.add(change);
144             }
145         }
146         createChangeCategoryPreference(enabledChanges, configMappings,
147                 getString(R.string.platform_compat_default_enabled_title));
148         createChangeCategoryPreference(disabledChanges, configMappings,
149                 getString(R.string.platform_compat_default_disabled_title));
150         for (Integer sdk : targetSdkChanges.keySet()) {
151             createChangeCategoryPreference(targetSdkChanges.get(sdk), configMappings,
152                     getString(R.string.platform_compat_target_sdk_title, sdk));
153         }
154     }
155 
getAppChangeMappings()156     private CompatibilityChangeConfig getAppChangeMappings() {
157         try {
158             final ApplicationInfo applicationInfo = getApplicationInfo();
159             return getPlatformCompat().getAppConfig(applicationInfo);
160         } catch (RemoteException | PackageManager.NameNotFoundException e) {
161             throw new RuntimeException("Could not get app config!", e);
162         }
163     }
164 
165     /**
166      * Create a {@link Preference} for a changeId.
167      *
168      * <p>The {@link Preference} is a toggle switch that can enable or disable the given change for
169      * the currently selected app.</p>
170      */
createPreferenceForChange(Context context, CompatibilityChangeInfo change, CompatibilityChangeConfig configMappings)171     Preference createPreferenceForChange(Context context, CompatibilityChangeInfo change,
172             CompatibilityChangeConfig configMappings) {
173         final boolean currentValue = configMappings.isChangeEnabled(change.getId());
174         final SwitchPreference item = new SwitchPreference(context);
175         final String changeName =
176                 change.getName() != null ? change.getName() : "Change_" + change.getId();
177         item.setSummary(changeName);
178         item.setKey(changeName);
179         boolean shouldEnable = true;
180         try {
181             shouldEnable = getPlatformCompat().getOverrideValidator()
182                            .getOverrideAllowedState(change.getId(), mSelectedApp)
183                            .state == ALLOWED;
184         } catch (RemoteException e) {
185             throw new RuntimeException("Could not check if change can be overridden for app.", e);
186         }
187         item.setEnabled(shouldEnable);
188         item.setChecked(currentValue);
189         item.setOnPreferenceChangeListener(
190                 new CompatChangePreferenceChangeListener(change.getId()));
191         return item;
192     }
193 
194     /**
195      * Get {@link ApplicationInfo} for the currently selected app.
196      *
197      * @return an {@link ApplicationInfo} instance.
198      */
getApplicationInfo()199     ApplicationInfo getApplicationInfo() throws PackageManager.NameNotFoundException {
200         return getPackageManager().getApplicationInfo(mSelectedApp, 0);
201     }
202 
203     /**
204      * Create a {@link Preference} for the selected app.
205      *
206      * <p>The {@link Preference} contains the icon, package name and target SDK for the selected
207      * app. Selecting this preference will also re-trigger the app selection dialog.</p>
208      */
createAppPreference(ApplicationInfo applicationInfo)209     Preference createAppPreference(ApplicationInfo applicationInfo) {
210         final Context context = getPreferenceScreen().getContext();
211         final Drawable icon = applicationInfo.loadIcon(context.getPackageManager());
212         final Preference appPreference = new Preference(context);
213         appPreference.setIcon(icon);
214         appPreference.setSummary(getString(R.string.platform_compat_selected_app_summary,
215                                          mSelectedApp, applicationInfo.targetSdkVersion));
216         return appPreference;
217     }
218 
createChangeCategoryPreference(List<CompatibilityChangeInfo> changes, CompatibilityChangeConfig configMappings, String title)219     PreferenceCategory createChangeCategoryPreference(List<CompatibilityChangeInfo> changes,
220             CompatibilityChangeConfig configMappings, String title) {
221         final PreferenceCategory category =
222                 new PreferenceCategory(getPreferenceScreen().getContext());
223         category.setTitle(title);
224         getPreferenceScreen().addPreference(category);
225         addChangePreferencesToCategory(changes, category, configMappings);
226         return category;
227     }
228 
addChangePreferencesToCategory(List<CompatibilityChangeInfo> changes, PreferenceCategory category, CompatibilityChangeConfig configMappings)229     private void addChangePreferencesToCategory(List<CompatibilityChangeInfo> changes,
230             PreferenceCategory category, CompatibilityChangeConfig configMappings) {
231         for (CompatibilityChangeInfo change : changes) {
232             final Preference preference = createPreferenceForChange(getPreferenceScreen().getContext(),
233                     change, configMappings);
234             category.addPreference(preference);
235         }
236     }
237 
238     private class CompatChangePreferenceChangeListener implements OnPreferenceChangeListener {
239         private final long changeId;
240 
CompatChangePreferenceChangeListener(long changeId)241         CompatChangePreferenceChangeListener(long changeId) {
242             this.changeId = changeId;
243         }
244 
245         @Override
onPreferenceChange(Preference preference, Object newValue)246         public boolean onPreferenceChange(Preference preference, Object newValue) {
247             try {
248                 final ArraySet<Long> enabled = new ArraySet<>();
249                 final ArraySet<Long> disabled = new ArraySet<>();
250                 if ((Boolean) newValue) {
251                     enabled.add(changeId);
252                 } else {
253                     disabled.add(changeId);
254                 }
255                 final CompatibilityChangeConfig overrides =
256                         new CompatibilityChangeConfig(new ChangeConfig(enabled, disabled));
257                 getPlatformCompat().setOverrides(overrides, mSelectedApp);
258             } catch (RemoteException e) {
259                 e.printStackTrace();
260                 return false;
261             }
262             return true;
263         }
264     }
265 }
266