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