1 /* 2 * Copyright (C) 2023 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.applications.appcompat; 18 19 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; 20 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 21 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9; 22 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; 23 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3; 24 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT; 25 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE; 26 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN; 27 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN; 28 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET; 29 30 import android.app.ActivityManager; 31 import android.app.IActivityManager; 32 import android.app.settings.SettingsEnums; 33 import android.content.Intent; 34 import android.content.pm.PackageManager; 35 import android.os.Build; 36 import android.os.Bundle; 37 import android.os.RemoteException; 38 import android.os.UserHandle; 39 import android.util.Log; 40 41 import androidx.annotation.NonNull; 42 import androidx.appcompat.app.AlertDialog; 43 import androidx.preference.Preference; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.settings.R; 47 import com.android.settings.Utils; 48 import com.android.settings.applications.AppInfoBase; 49 import com.android.settings.overlay.FeatureFactory; 50 import com.android.settings.widget.EntityHeaderController; 51 import com.android.settingslib.applications.AppUtils; 52 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 53 import com.android.settingslib.widget.ActionButtonsPreference; 54 55 import com.google.common.collect.BiMap; 56 import com.google.common.collect.HashBiMap; 57 58 import java.util.ArrayList; 59 import java.util.List; 60 61 /** 62 * App specific activity to show aspect ratio overrides 63 */ 64 public class UserAspectRatioDetails extends AppInfoBase implements 65 RadioWithImagePreference.OnClickListener { 66 private static final String TAG = UserAspectRatioDetails.class.getSimpleName(); 67 68 private static final String KEY_HEADER_SUMMARY = "app_aspect_ratio_summary"; 69 @VisibleForTesting 70 static final String KEY_HEADER_BUTTONS = "header_view"; 71 72 private static final String KEY_PREF_HALF_SCREEN = "half_screen_pref"; 73 private static final String KEY_PREF_DISPLAY_SIZE = "display_size_pref"; 74 private static final String KEY_PREF_16_9 = "16_9_pref"; 75 private static final String KEY_PREF_4_3 = "4_3_pref"; 76 @VisibleForTesting 77 static final String KEY_PREF_FULLSCREEN = "fullscreen_pref"; 78 @VisibleForTesting 79 static final String KEY_PREF_DEFAULT = "app_default_pref"; 80 @VisibleForTesting 81 static final String KEY_PREF_3_2 = "3_2_pref"; 82 83 @VisibleForTesting 84 @NonNull String mSelectedKey = KEY_PREF_DEFAULT; 85 86 /** Radio button preference key mapped to {@link PackageManager.UserMinAspectRatio} value */ 87 @VisibleForTesting 88 final BiMap<String, Integer> mKeyToAspectRatioMap = HashBiMap.create(); 89 90 private final List<RadioWithImagePreference> mAspectRatioPreferences = new ArrayList<>(); 91 92 @NonNull private UserAspectRatioManager mUserAspectRatioManager; 93 private boolean mIsOverrideToFullscreenEnabled; 94 95 @Override onCreate(@onNull Bundle savedInstanceState)96 public void onCreate(@NonNull Bundle savedInstanceState) { 97 super.onCreate(savedInstanceState); 98 99 mUserAspectRatioManager = new UserAspectRatioManager(getContext()); 100 mIsOverrideToFullscreenEnabled = getAspectRatioManager() 101 .isOverrideToFullscreenEnabled(mPackageName, mUserId); 102 103 initPreferences(); 104 try { 105 final int userAspectRatio = getAspectRatioManager() 106 .getUserMinAspectRatioValue(mPackageName, mUserId); 107 mSelectedKey = getSelectedKey(userAspectRatio); 108 } catch (RemoteException e) { 109 Log.e(TAG, "Unable to get user min aspect ratio"); 110 } 111 refreshUi(); 112 } 113 114 @Override onRadioButtonClicked(@onNull RadioWithImagePreference selected)115 public void onRadioButtonClicked(@NonNull RadioWithImagePreference selected) { 116 final String selectedKey = selected.getKey(); 117 if (mSelectedKey.equals(selectedKey)) { 118 return; 119 } 120 final int userAspectRatio = getSelectedUserMinAspectRatio(selectedKey); 121 try { 122 getAspectRatioManager().setUserMinAspectRatio(mPackageName, mUserId, userAspectRatio); 123 } catch (RemoteException e) { 124 Log.e(TAG, "Unable to set user min aspect ratio"); 125 return; 126 } 127 logActionMetrics(selectedKey, mSelectedKey); 128 // Only update to selected aspect ratio if nothing goes wrong 129 mSelectedKey = selectedKey; 130 updateAllPreferences(mSelectedKey); 131 Log.d(TAG, "Killing application process " + mPackageName); 132 try { 133 final IActivityManager am = ActivityManager.getService(); 134 am.stopAppForUser(mPackageName, mUserId); 135 } catch (RemoteException e) { 136 Log.e(TAG, "Unable to stop application " + mPackageName); 137 } 138 } 139 140 @Override getMetricsCategory()141 public int getMetricsCategory() { 142 return SettingsEnums.USER_ASPECT_RATIO_APP_INFO_SETTINGS; 143 } 144 145 @Override refreshUi()146 protected boolean refreshUi() { 147 if (mPackageInfo == null || mPackageInfo.applicationInfo == null) { 148 return false; 149 } 150 updateAllPreferences(mSelectedKey); 151 return true; 152 } 153 154 @Override createDialog(int id, int errorCode)155 protected AlertDialog createDialog(int id, int errorCode) { 156 return null; 157 } 158 launchApplication()159 private void launchApplication() { 160 Intent launchIntent = mPm.getLaunchIntentForPackage(mPackageName) 161 .addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP); 162 if (launchIntent != null) { 163 getContext().startActivityAsUser(launchIntent, new UserHandle(mUserId)); 164 } 165 } 166 167 @PackageManager.UserMinAspectRatio 168 @VisibleForTesting getSelectedUserMinAspectRatio(@onNull String selectedKey)169 int getSelectedUserMinAspectRatio(@NonNull String selectedKey) { 170 final int appDefault = mKeyToAspectRatioMap 171 .getOrDefault(KEY_PREF_DEFAULT, USER_MIN_ASPECT_RATIO_UNSET); 172 return mKeyToAspectRatioMap.getOrDefault(selectedKey, appDefault); 173 } 174 175 @NonNull getSelectedKey(@ackageManager.UserMinAspectRatio int userMinAspectRatio)176 private String getSelectedKey(@PackageManager.UserMinAspectRatio int userMinAspectRatio) { 177 final String appDefault = mKeyToAspectRatioMap.inverse() 178 .getOrDefault(USER_MIN_ASPECT_RATIO_UNSET, KEY_PREF_DEFAULT); 179 180 if (userMinAspectRatio == USER_MIN_ASPECT_RATIO_UNSET && mIsOverrideToFullscreenEnabled) { 181 // Pre-select fullscreen option if device manufacturer has overridden app to fullscreen 182 userMinAspectRatio = USER_MIN_ASPECT_RATIO_FULLSCREEN; 183 } 184 return mKeyToAspectRatioMap.inverse().getOrDefault(userMinAspectRatio, appDefault); 185 } 186 187 @Override onActivityCreated(Bundle savedInstanceState)188 public void onActivityCreated(Bundle savedInstanceState) { 189 super.onActivityCreated(savedInstanceState); 190 final Preference pref = EntityHeaderController 191 .newInstance(getActivity(), this, null /* header */) 192 .setIcon(Utils.getBadgedIcon(getContext(), mPackageInfo.applicationInfo)) 193 .setLabel(mPackageInfo.applicationInfo.loadLabel(mPm)) 194 .setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo)) 195 .setPackageName(mPackageName) 196 .setUid(mPackageInfo.applicationInfo.uid) 197 .setHasAppInfoLink(true) 198 .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE, 199 EntityHeaderController.ActionType.ACTION_NONE) 200 .done(getPrefContext()); 201 202 getPreferenceScreen().addPreference(pref); 203 } 204 initPreferences()205 private void initPreferences() { 206 addPreferencesFromResource(R.xml.user_aspect_ratio_details); 207 208 final String summary = getContext().getResources().getString( 209 R.string.aspect_ratio_main_summary, Build.MODEL); 210 findPreference(KEY_HEADER_SUMMARY).setTitle(summary); 211 212 ((ActionButtonsPreference) findPreference(KEY_HEADER_BUTTONS)) 213 .setButton1Text(R.string.launch_instant_app) 214 .setButton1Icon(R.drawable.ic_settings_open) 215 .setButton1OnClickListener(v -> launchApplication()); 216 217 if (mIsOverrideToFullscreenEnabled) { 218 addPreference(KEY_PREF_DEFAULT, USER_MIN_ASPECT_RATIO_APP_DEFAULT); 219 } else { 220 addPreference(KEY_PREF_DEFAULT, USER_MIN_ASPECT_RATIO_UNSET); 221 } 222 addPreference(KEY_PREF_FULLSCREEN, USER_MIN_ASPECT_RATIO_FULLSCREEN); 223 addPreference(KEY_PREF_DISPLAY_SIZE, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE); 224 addPreference(KEY_PREF_HALF_SCREEN, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN); 225 addPreference(KEY_PREF_16_9, USER_MIN_ASPECT_RATIO_16_9); 226 addPreference(KEY_PREF_4_3, USER_MIN_ASPECT_RATIO_4_3); 227 addPreference(KEY_PREF_3_2, USER_MIN_ASPECT_RATIO_3_2); 228 } 229 addPreference(@onNull String key, @PackageManager.UserMinAspectRatio int aspectRatio)230 private void addPreference(@NonNull String key, 231 @PackageManager.UserMinAspectRatio int aspectRatio) { 232 final RadioWithImagePreference pref = findPreference(key); 233 if (pref == null) { 234 return; 235 } 236 if (!getAspectRatioManager().hasAspectRatioOption(aspectRatio, mPackageName)) { 237 pref.setVisible(false); 238 return; 239 } 240 pref.setTitle(mUserAspectRatioManager.getAccessibleEntry(aspectRatio, mPackageName)); 241 pref.setOrder(getAspectRatioManager().getUserMinAspectRatioOrder(aspectRatio)); 242 pref.setOnClickListener(this); 243 mKeyToAspectRatioMap.put(key, aspectRatio); 244 mAspectRatioPreferences.add(pref); 245 } 246 updateAllPreferences(@onNull String selectedKey)247 private void updateAllPreferences(@NonNull String selectedKey) { 248 for (RadioWithImagePreference pref : mAspectRatioPreferences) { 249 pref.setChecked(selectedKey.equals(pref.getKey())); 250 } 251 } 252 logActionMetrics(@onNull String selectedKey, @NonNull String unselectedKey)253 private void logActionMetrics(@NonNull String selectedKey, @NonNull String unselectedKey) { 254 final MetricsFeatureProvider metricsFeatureProvider = 255 FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); 256 final int attribution = metricsFeatureProvider.getAttribution(getActivity()); 257 metricsFeatureProvider.action( 258 attribution, 259 getUnselectedAspectRatioAction(unselectedKey), 260 getMetricsCategory(), 261 mPackageName, 262 mUserId 263 ); 264 metricsFeatureProvider.action( 265 attribution, 266 getSelectedAspectRatioAction(selectedKey), 267 getMetricsCategory(), 268 mPackageName, 269 mUserId 270 ); 271 } 272 getSelectedAspectRatioAction(@onNull String selectedKey)273 private static int getSelectedAspectRatioAction(@NonNull String selectedKey) { 274 switch (selectedKey) { 275 case KEY_PREF_DEFAULT: 276 return SettingsEnums.ACTION_USER_ASPECT_RATIO_APP_DEFAULT_SELECTED; 277 case KEY_PREF_FULLSCREEN: 278 return SettingsEnums.ACTION_USER_ASPECT_RATIO_FULL_SCREEN_SELECTED; 279 case KEY_PREF_HALF_SCREEN: 280 return SettingsEnums.ACTION_USER_ASPECT_RATIO_HALF_SCREEN_SELECTED; 281 case KEY_PREF_4_3: 282 return SettingsEnums.ACTION_USER_ASPECT_RATIO_4_3_SELECTED; 283 case KEY_PREF_16_9: 284 return SettingsEnums.ACTION_USER_ASPECT_RATIO_16_9_SELECTED; 285 case KEY_PREF_3_2: 286 return SettingsEnums.ACTION_USER_ASPECT_RATIO_3_2_SELECTED; 287 case KEY_PREF_DISPLAY_SIZE: 288 return SettingsEnums.ACTION_USER_ASPECT_RATIO_DISPLAY_SIZE_SELECTED; 289 default: 290 return SettingsEnums.ACTION_UNKNOWN; 291 } 292 } 293 getUnselectedAspectRatioAction(@onNull String unselectedKey)294 private static int getUnselectedAspectRatioAction(@NonNull String unselectedKey) { 295 switch (unselectedKey) { 296 case KEY_PREF_DEFAULT: 297 return SettingsEnums.ACTION_USER_ASPECT_RATIO_APP_DEFAULT_UNSELECTED; 298 case KEY_PREF_FULLSCREEN: 299 return SettingsEnums.ACTION_USER_ASPECT_RATIO_FULL_SCREEN_UNSELECTED; 300 case KEY_PREF_HALF_SCREEN: 301 return SettingsEnums.ACTION_USER_ASPECT_RATIO_HALF_SCREEN_UNSELECTED; 302 case KEY_PREF_4_3: 303 return SettingsEnums.ACTION_USER_ASPECT_RATIO_4_3_UNSELECTED; 304 case KEY_PREF_16_9: 305 return SettingsEnums.ACTION_USER_ASPECT_RATIO_16_9_UNSELECTED; 306 case KEY_PREF_3_2: 307 return SettingsEnums.ACTION_USER_ASPECT_RATIO_3_2_UNSELECTED; 308 case KEY_PREF_DISPLAY_SIZE: 309 return SettingsEnums.ACTION_USER_ASPECT_RATIO_DISPLAY_SIZE_UNSELECTED; 310 default: 311 return SettingsEnums.ACTION_UNKNOWN; 312 } 313 } 314 315 @VisibleForTesting getAspectRatioManager()316 UserAspectRatioManager getAspectRatioManager() { 317 return mUserAspectRatioManager; 318 } 319 } 320