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