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