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