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 super.onCreatePreferences(savedInstanceState, rootKey); 90 try { 91 // Check if the xml specifies if static preferences should go on the top or bottom 92 final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(getContext(), 93 getPreferenceScreenResId(), 94 MetadataFlag.FLAG_INCLUDE_PREF_SCREEN | 95 MetadataFlag.FLAG_NEED_PREF_APPEND); 96 mAppendStaticPreferences = metadata.get(0) 97 .getBoolean(PreferenceXmlParserUtils.METADATA_APPEND); 98 } catch (IOException e) { 99 Log.e(TAG, "Error trying to open xml file", e); 100 } catch (XmlPullParserException e) { 101 Log.e(TAG, "Error parsing xml", e); 102 } 103 updateCandidates(); 104 } 105 106 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)107 public View onCreateView(LayoutInflater inflater, ViewGroup container, 108 Bundle savedInstanceState) { 109 final View view = super.onCreateView(inflater, container, savedInstanceState); 110 setHasOptionsMenu(true); 111 return view; 112 } 113 114 @Override getPreferenceScreenResId()115 protected abstract int getPreferenceScreenResId(); 116 117 @Override onRadioButtonClicked(SelectorWithWidgetPreference selected)118 public void onRadioButtonClicked(SelectorWithWidgetPreference selected) { 119 final String selectedKey = selected.getKey(); 120 onRadioButtonConfirmed(selectedKey); 121 } 122 123 /** 124 * Called after the user tries to select an item. 125 */ onSelectionPerformed(boolean success)126 protected void onSelectionPerformed(boolean success) { 127 } 128 129 /** 130 * Whether the UI should show a "None" item selection. 131 */ shouldShowItemNone()132 protected boolean shouldShowItemNone() { 133 return false; 134 } 135 136 /** 137 * Populate any static preferences, independent of the radio buttons. 138 * These might be used to provide extra information about the choices. 139 **/ addStaticPreferences(PreferenceScreen screen)140 protected void addStaticPreferences(PreferenceScreen screen) { 141 } 142 getCandidate(String key)143 protected CandidateInfo getCandidate(String key) { 144 return mCandidates.get(key); 145 } 146 onRadioButtonConfirmed(String selectedKey)147 protected void onRadioButtonConfirmed(String selectedKey) { 148 final boolean success = setDefaultKey(selectedKey); 149 if (success) { 150 updateCheckedState(selectedKey); 151 } 152 onSelectionPerformed(success); 153 } 154 155 /** 156 * A chance for subclasses to bind additional things to the preference. 157 */ bindPreferenceExtra(SelectorWithWidgetPreference pref, String key, CandidateInfo info, String defaultKey, String systemDefaultKey)158 public void bindPreferenceExtra(SelectorWithWidgetPreference pref, 159 String key, CandidateInfo info, String defaultKey, String systemDefaultKey) { 160 } 161 162 /** 163 * A chance for subclasses to create a custom preference instance. 164 */ createPreference()165 protected SelectorWithWidgetPreference createPreference() { 166 return new SelectorWithWidgetPreference(getPrefContext()); 167 } 168 updateCandidates()169 public void updateCandidates() { 170 mCandidates.clear(); 171 final List<? extends CandidateInfo> candidateList = getCandidates(); 172 if (candidateList != null) { 173 for (CandidateInfo info : candidateList) { 174 mCandidates.put(info.getKey(), info); 175 } 176 } 177 final String defaultKey = getDefaultKey(); 178 final String systemDefaultKey = getSystemDefaultKey(); 179 final PreferenceScreen screen = getPreferenceScreen(); 180 screen.removeAll(); 181 if (mIllustrationId != 0) { 182 addIllustration(screen); 183 } 184 if (!mAppendStaticPreferences) { 185 addStaticPreferences(screen); 186 } 187 188 final int customLayoutResId = getRadioButtonPreferenceCustomLayoutResId(); 189 if (shouldShowItemNone()) { 190 final SelectorWithWidgetPreference nonePref = 191 new SelectorWithWidgetPreference(getPrefContext()); 192 if (customLayoutResId > 0) { 193 nonePref.setLayoutResource(customLayoutResId); 194 } 195 nonePref.setIcon(R.drawable.ic_remove_circle); 196 nonePref.setTitle(R.string.app_list_preference_none); 197 nonePref.setChecked(TextUtils.isEmpty(defaultKey)); 198 nonePref.setOnClickListener(this); 199 screen.addPreference(nonePref); 200 } 201 if (candidateList != null) { 202 for (CandidateInfo info : candidateList) { 203 SelectorWithWidgetPreference pref = createPreference(); 204 if (customLayoutResId > 0) { 205 pref.setLayoutResource(customLayoutResId); 206 } 207 bindPreference(pref, info.getKey(), info, defaultKey); 208 bindPreferenceExtra(pref, info.getKey(), info, defaultKey, systemDefaultKey); 209 screen.addPreference(pref); 210 } 211 } 212 mayCheckOnlyRadioButton(); 213 if (mAppendStaticPreferences) { 214 addStaticPreferences(screen); 215 } 216 } 217 bindPreference(SelectorWithWidgetPreference pref, String key, CandidateInfo info, String defaultKey)218 public SelectorWithWidgetPreference bindPreference(SelectorWithWidgetPreference pref, 219 String key, CandidateInfo info, String defaultKey) { 220 pref.setTitle(info.loadLabel()); 221 pref.setIcon(Utils.getSafeIcon(info.loadIcon())); 222 pref.setKey(key); 223 if (TextUtils.equals(defaultKey, key)) { 224 pref.setChecked(true); 225 } 226 pref.setEnabled(info.enabled); 227 pref.setOnClickListener(this); 228 return pref; 229 } 230 updateCheckedState(String selectedKey)231 public void updateCheckedState(String selectedKey) { 232 final PreferenceScreen screen = getPreferenceScreen(); 233 if (screen != null) { 234 final int count = screen.getPreferenceCount(); 235 for (int i = 0; i < count; i++) { 236 final Preference pref = screen.getPreference(i); 237 if (pref instanceof SelectorWithWidgetPreference) { 238 final SelectorWithWidgetPreference radioPref = 239 (SelectorWithWidgetPreference) pref; 240 final boolean newCheckedState = TextUtils.equals(pref.getKey(), selectedKey); 241 if (radioPref.isChecked() != newCheckedState) { 242 radioPref.setChecked(TextUtils.equals(pref.getKey(), selectedKey)); 243 } 244 } 245 } 246 } 247 } 248 mayCheckOnlyRadioButton()249 public void mayCheckOnlyRadioButton() { 250 final PreferenceScreen screen = getPreferenceScreen(); 251 // If there is only 1 thing on screen, select it. 252 if (screen != null && screen.getPreferenceCount() == 1) { 253 final Preference onlyPref = screen.getPreference(0); 254 if (onlyPref instanceof SelectorWithWidgetPreference) { 255 ((SelectorWithWidgetPreference) onlyPref).setChecked(true); 256 } 257 } 258 } 259 260 /** 261 * Allows you to set an illustration at the top of this screen. Set the illustration id to 0 262 * if you want to remove the illustration. 263 * 264 * @param illustrationId The res id for the raw of the illustration. 265 * @param previewId The res id for the drawable of the illustration. 266 * @param illustrationType The illustration type for the raw of the illustration. 267 */ setIllustration(@nyRes int illustrationId, @AnyRes int previewId, IllustrationType illustrationType)268 protected void setIllustration(@AnyRes int illustrationId, @AnyRes int previewId, 269 IllustrationType illustrationType) { 270 mIllustrationId = illustrationId; 271 mIllustrationPreviewId = previewId; 272 mIllustrationType = illustrationType; 273 } 274 275 /** 276 * Allows you to set an illustration at the top of this screen. Set the illustration id to 0 277 * if you want to remove the illustration. 278 * 279 * @param illustrationId The res id for the raw of the illustration. 280 * @param illustrationType The illustration type for the raw of the illustration. 281 */ setIllustration(@nyRes int illustrationId, IllustrationType illustrationType)282 protected void setIllustration(@AnyRes int illustrationId, IllustrationType illustrationType) { 283 setIllustration(illustrationId, 0, illustrationType); 284 } 285 addIllustration(PreferenceScreen screen)286 private void addIllustration(PreferenceScreen screen) { 287 switch (mIllustrationType) { 288 case LOTTIE_ANIMATION: 289 IllustrationPreference illustrationPreference = new IllustrationPreference( 290 getContext()); 291 illustrationPreference.setLottieAnimationResId(mIllustrationId); 292 screen.addPreference(illustrationPreference); 293 break; 294 default: 295 throw new IllegalArgumentException( 296 "Invalid illustration type: " + mIllustrationType); 297 } 298 } 299 getCandidates()300 protected abstract List<? extends CandidateInfo> getCandidates(); 301 getDefaultKey()302 protected abstract String getDefaultKey(); 303 setDefaultKey(String key)304 protected abstract boolean setDefaultKey(String key); 305 getSystemDefaultKey()306 protected String getSystemDefaultKey() { 307 return null; 308 } 309 310 /** 311 * Provides a custom layout for each candidate row. 312 */ 313 @LayoutRes getRadioButtonPreferenceCustomLayoutResId()314 protected int getRadioButtonPreferenceCustomLayoutResId() { 315 return 0; 316 } 317 318 protected enum IllustrationType { 319 LOTTIE_ANIMATION 320 } 321 322 } 323