1 /* 2 * Copyright (C) 2017 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.widget; 18 19 import android.annotation.AnyRes; 20 import android.content.Context; 21 import android.os.Bundle; 22 import android.os.UserHandle; 23 import android.os.UserManager; 24 import android.text.TextUtils; 25 import android.util.ArrayMap; 26 import android.util.Log; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.view.ViewGroup; 30 31 import androidx.annotation.LayoutRes; 32 import androidx.annotation.VisibleForTesting; 33 import androidx.preference.Preference; 34 import androidx.preference.PreferenceScreen; 35 36 import com.android.settings.R; 37 import com.android.settings.SettingsPreferenceFragment; 38 import com.android.settings.Utils; 39 import com.android.settings.core.PreferenceXmlParserUtils; 40 import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag; 41 import com.android.settingslib.widget.CandidateInfo; 42 import com.android.settingslib.widget.IllustrationPreference; 43 import com.android.settingslib.widget.SelectorWithWidgetPreference; 44 45 import org.xmlpull.v1.XmlPullParserException; 46 47 import java.io.IOException; 48 import java.util.List; 49 import java.util.Map; 50 51 /** 52 * A fragment to handle general radio button picker 53 */ 54 public abstract class RadioButtonPickerFragment extends SettingsPreferenceFragment implements 55 SelectorWithWidgetPreference.OnClickListener { 56 57 @VisibleForTesting 58 static final String EXTRA_FOR_WORK = "for_work"; 59 private static final String TAG = "RadioButtonPckrFrgmt"; 60 @VisibleForTesting 61 boolean mAppendStaticPreferences = false; 62 63 private final Map<String, CandidateInfo> mCandidates = new ArrayMap<>(); 64 65 protected UserManager mUserManager; 66 protected int mUserId; 67 private int mIllustrationId; 68 private int mIllustrationPreviewId; 69 private IllustrationType mIllustrationType; 70 71 @Override onAttach(Context context)72 public void onAttach(Context context) { 73 super.onAttach(context); 74 mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); 75 final Bundle arguments = getArguments(); 76 77 boolean mForWork = false; 78 if (arguments != null) { 79 mForWork = arguments.getBoolean(EXTRA_FOR_WORK); 80 } 81 final UserHandle managedProfile = Utils.getManagedProfile(mUserManager); 82 mUserId = mForWork && managedProfile != null 83 ? managedProfile.getIdentifier() 84 : UserHandle.myUserId(); 85 } 86 87 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)88 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 89 if (isCatalystEnabled()) { 90 PreferenceScreen preferenceScreen = createPreferenceScreen(); 91 setPreferenceScreen(preferenceScreen); 92 } else { 93 super.onCreatePreferences(savedInstanceState, rootKey); 94 } 95 try { 96 // Check if the xml specifies if static preferences should go on the top or bottom 97 final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(getContext(), 98 getPreferenceScreenResId(), 99 MetadataFlag.FLAG_INCLUDE_PREF_SCREEN | 100 MetadataFlag.FLAG_NEED_PREF_APPEND); 101 mAppendStaticPreferences = metadata.get(0) 102 .getBoolean(PreferenceXmlParserUtils.METADATA_APPEND); 103 } catch (IOException e) { 104 Log.e(TAG, "Error trying to open xml file", e); 105 } catch (XmlPullParserException e) { 106 Log.e(TAG, "Error parsing xml", e); 107 } 108 updateCandidates(); 109 } 110 111 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)112 public View onCreateView(LayoutInflater inflater, ViewGroup container, 113 Bundle savedInstanceState) { 114 final View view = super.onCreateView(inflater, container, savedInstanceState); 115 setHasOptionsMenu(true); 116 return view; 117 } 118 119 @Override getPreferenceScreenResId()120 protected abstract int getPreferenceScreenResId(); 121 122 @Override onRadioButtonClicked(SelectorWithWidgetPreference selected)123 public void onRadioButtonClicked(SelectorWithWidgetPreference selected) { 124 final String selectedKey = selected.getKey(); 125 onRadioButtonConfirmed(selectedKey); 126 } 127 128 /** 129 * Called after the user tries to select an item. 130 */ onSelectionPerformed(boolean success)131 protected void onSelectionPerformed(boolean success) { 132 } 133 134 /** 135 * Whether the UI should show a "None" item selection. 136 */ shouldShowItemNone()137 protected boolean shouldShowItemNone() { 138 return false; 139 } 140 141 /** 142 * Populate any static preferences, independent of the radio buttons. 143 * These might be used to provide extra information about the choices. 144 **/ addStaticPreferences(PreferenceScreen screen)145 protected void addStaticPreferences(PreferenceScreen screen) { 146 } 147 getCandidate(String key)148 protected CandidateInfo getCandidate(String key) { 149 return mCandidates.get(key); 150 } 151 onRadioButtonConfirmed(String selectedKey)152 protected void onRadioButtonConfirmed(String selectedKey) { 153 final boolean success = setDefaultKey(selectedKey); 154 if (success) { 155 updateCheckedState(selectedKey); 156 } 157 onSelectionPerformed(success); 158 } 159 160 /** 161 * A chance for subclasses to bind additional things to the preference. 162 */ bindPreferenceExtra(SelectorWithWidgetPreference pref, String key, CandidateInfo info, String defaultKey, String systemDefaultKey)163 public void bindPreferenceExtra(SelectorWithWidgetPreference pref, 164 String key, CandidateInfo info, String defaultKey, String systemDefaultKey) { 165 } 166 167 /** 168 * A chance for subclasses to create a custom preference instance. 169 */ createPreference()170 protected SelectorWithWidgetPreference createPreference() { 171 return new SelectorWithWidgetPreference(getPrefContext()); 172 } 173 updateCandidates()174 public void updateCandidates() { 175 mCandidates.clear(); 176 final List<? extends CandidateInfo> candidateList = getCandidates(); 177 if (candidateList != null) { 178 for (CandidateInfo info : candidateList) { 179 mCandidates.put(info.getKey(), info); 180 } 181 } 182 final String defaultKey = getDefaultKey(); 183 final String systemDefaultKey = getSystemDefaultKey(); 184 final PreferenceScreen screen = getPreferenceScreen(); 185 screen.removeAll(); 186 if (mIllustrationId != 0) { 187 addIllustration(screen); 188 } 189 if (!mAppendStaticPreferences) { 190 addStaticPreferences(screen); 191 } 192 193 final int customLayoutResId = getRadioButtonPreferenceCustomLayoutResId(); 194 if (shouldShowItemNone()) { 195 final SelectorWithWidgetPreference nonePref = 196 new SelectorWithWidgetPreference(getPrefContext()); 197 if (customLayoutResId > 0) { 198 nonePref.setLayoutResource(customLayoutResId); 199 } 200 nonePref.setIcon(R.drawable.ic_remove_circle); 201 nonePref.setTitle(R.string.app_list_preference_none); 202 nonePref.setChecked(TextUtils.isEmpty(defaultKey)); 203 nonePref.setOnClickListener(this); 204 screen.addPreference(nonePref); 205 } 206 if (candidateList != null) { 207 for (CandidateInfo info : candidateList) { 208 SelectorWithWidgetPreference pref = createPreference(); 209 if (customLayoutResId > 0) { 210 pref.setLayoutResource(customLayoutResId); 211 } 212 bindPreference(pref, info.getKey(), info, defaultKey); 213 bindPreferenceExtra(pref, info.getKey(), info, defaultKey, systemDefaultKey); 214 screen.addPreference(pref); 215 } 216 } 217 mayCheckOnlyRadioButton(); 218 if (mAppendStaticPreferences) { 219 addStaticPreferences(screen); 220 } 221 } 222 bindPreference(SelectorWithWidgetPreference pref, String key, CandidateInfo info, String defaultKey)223 public SelectorWithWidgetPreference bindPreference(SelectorWithWidgetPreference pref, 224 String key, CandidateInfo info, String defaultKey) { 225 pref.setTitle(info.loadLabel()); 226 pref.setIcon(Utils.getSafeIcon(info.loadIcon())); 227 pref.setKey(key); 228 if (TextUtils.equals(defaultKey, key)) { 229 pref.setChecked(true); 230 } 231 pref.setEnabled(info.enabled); 232 pref.setOnClickListener(this); 233 return pref; 234 } 235 updateCheckedState(String selectedKey)236 public void updateCheckedState(String selectedKey) { 237 final PreferenceScreen screen = getPreferenceScreen(); 238 if (screen != null) { 239 final int count = screen.getPreferenceCount(); 240 for (int i = 0; i < count; i++) { 241 final Preference pref = screen.getPreference(i); 242 if (pref instanceof SelectorWithWidgetPreference) { 243 final SelectorWithWidgetPreference radioPref = 244 (SelectorWithWidgetPreference) pref; 245 final boolean newCheckedState = TextUtils.equals(pref.getKey(), selectedKey); 246 if (radioPref.isChecked() != newCheckedState) { 247 radioPref.setChecked(TextUtils.equals(pref.getKey(), selectedKey)); 248 } 249 } 250 } 251 } 252 } 253 mayCheckOnlyRadioButton()254 public void mayCheckOnlyRadioButton() { 255 final PreferenceScreen screen = getPreferenceScreen(); 256 // If there is only 1 thing on screen, select it. 257 if (screen != null && screen.getPreferenceCount() == 1) { 258 final Preference onlyPref = screen.getPreference(0); 259 if (onlyPref instanceof SelectorWithWidgetPreference) { 260 ((SelectorWithWidgetPreference) onlyPref).setChecked(true); 261 } 262 } 263 } 264 265 /** 266 * Allows you to set an illustration at the top of this screen. Set the illustration id to 0 267 * if you want to remove the illustration. 268 * 269 * @param illustrationId The res id for the raw of the illustration. 270 * @param previewId The res id for the drawable of the illustration. 271 * @param illustrationType The illustration type for the raw of the illustration. 272 */ setIllustration(@nyRes int illustrationId, @AnyRes int previewId, IllustrationType illustrationType)273 protected void setIllustration(@AnyRes int illustrationId, @AnyRes int previewId, 274 IllustrationType illustrationType) { 275 mIllustrationId = illustrationId; 276 mIllustrationPreviewId = previewId; 277 mIllustrationType = illustrationType; 278 } 279 280 /** 281 * Allows you to set an illustration at the top of this screen. Set the illustration id to 0 282 * if you want to remove the illustration. 283 * 284 * @param illustrationId The res id for the raw of the illustration. 285 * @param illustrationType The illustration type for the raw of the illustration. 286 */ setIllustration(@nyRes int illustrationId, IllustrationType illustrationType)287 protected void setIllustration(@AnyRes int illustrationId, IllustrationType illustrationType) { 288 setIllustration(illustrationId, 0, illustrationType); 289 } 290 addIllustration(PreferenceScreen screen)291 private void addIllustration(PreferenceScreen screen) { 292 switch (mIllustrationType) { 293 case LOTTIE_ANIMATION: 294 IllustrationPreference illustrationPreference = new IllustrationPreference( 295 getContext()); 296 illustrationPreference.setLottieAnimationResId(mIllustrationId); 297 screen.addPreference(illustrationPreference); 298 break; 299 default: 300 throw new IllegalArgumentException( 301 "Invalid illustration type: " + mIllustrationType); 302 } 303 } 304 getCandidates()305 protected abstract List<? extends CandidateInfo> getCandidates(); 306 getDefaultKey()307 protected abstract String getDefaultKey(); 308 setDefaultKey(String key)309 protected abstract boolean setDefaultKey(String key); 310 getSystemDefaultKey()311 protected String getSystemDefaultKey() { 312 return null; 313 } 314 315 /** 316 * Provides a custom layout for each candidate row. 317 */ 318 @LayoutRes getRadioButtonPreferenceCustomLayoutResId()319 protected int getRadioButtonPreferenceCustomLayoutResId() { 320 return 0; 321 } 322 323 protected enum IllustrationType { 324 LOTTIE_ANIMATION 325 } 326 327 } 328