1/* 2 * Copyright (C) 2015 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 17package android.support.v7.preference; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.os.Bundle; 22import android.support.v4.content.res.TypedArrayUtils; 23import android.text.TextUtils; 24import android.util.AttributeSet; 25 26import java.util.ArrayList; 27import java.util.Collections; 28import java.util.List; 29 30/** 31 * A container for multiple 32 * {@link Preference} objects. It is a base class for Preference objects that are 33 * parents, such as {@link PreferenceCategory} and {@link PreferenceScreen}. 34 * 35 * <div class="special reference"> 36 * <h3>Developer Guides</h3> 37 * <p>For information about building a settings UI with Preferences, 38 * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a> 39 * guide.</p> 40 * </div> 41 * 42 * @attr ref android.R.styleable#PreferenceGroup_orderingFromXml 43 */ 44public abstract class PreferenceGroup extends Preference { 45 /** 46 * The container for child {@link Preference}s. This is sorted based on the 47 * ordering, please use {@link #addPreference(Preference)} instead of adding 48 * to this directly. 49 */ 50 private List<Preference> mPreferenceList; 51 52 private boolean mOrderingAsAdded = true; 53 54 private int mCurrentPreferenceOrder = 0; 55 56 private boolean mAttachedToHierarchy = false; 57 58 public PreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 59 super(context, attrs, defStyleAttr, defStyleRes); 60 61 mPreferenceList = new ArrayList<>(); 62 63 final TypedArray a = context.obtainStyledAttributes( 64 attrs, R.styleable.PreferenceGroup, defStyleAttr, defStyleRes); 65 66 mOrderingAsAdded = 67 TypedArrayUtils.getBoolean(a, R.styleable.PreferenceGroup_orderingFromXml, 68 R.styleable.PreferenceGroup_orderingFromXml, true); 69 70 a.recycle(); 71 } 72 73 public PreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr) { 74 this(context, attrs, defStyleAttr, 0); 75 } 76 77 public PreferenceGroup(Context context, AttributeSet attrs) { 78 this(context, attrs, 0); 79 } 80 81 /** 82 * Whether to order the {@link Preference} children of this group as they 83 * are added. If this is false, the ordering will follow each Preference 84 * order and default to alphabetic for those without an order. 85 * <p> 86 * If this is called after preferences are added, they will not be 87 * re-ordered in the order they were added, hence call this method early on. 88 * 89 * @param orderingAsAdded Whether to order according to the order added. 90 * @see Preference#setOrder(int) 91 */ 92 public void setOrderingAsAdded(boolean orderingAsAdded) { 93 mOrderingAsAdded = orderingAsAdded; 94 } 95 96 /** 97 * Whether this group is ordering preferences in the order they are added. 98 * 99 * @return Whether this group orders based on the order the children are added. 100 * @see #setOrderingAsAdded(boolean) 101 */ 102 public boolean isOrderingAsAdded() { 103 return mOrderingAsAdded; 104 } 105 106 /** 107 * Called by the inflater to add an item to this group. 108 */ 109 public void addItemFromInflater(Preference preference) { 110 addPreference(preference); 111 } 112 113 /** 114 * Returns the number of children {@link Preference}s. 115 * @return The number of preference children in this group. 116 */ 117 public int getPreferenceCount() { 118 return mPreferenceList.size(); 119 } 120 121 /** 122 * Returns the {@link Preference} at a particular index. 123 * 124 * @param index The index of the {@link Preference} to retrieve. 125 * @return The {@link Preference}. 126 */ 127 public Preference getPreference(int index) { 128 return mPreferenceList.get(index); 129 } 130 131 /** 132 * Adds a {@link Preference} at the correct position based on the 133 * preference's order. 134 * 135 * @param preference The preference to add. 136 * @return Whether the preference is now in this group. 137 */ 138 public boolean addPreference(Preference preference) { 139 if (mPreferenceList.contains(preference)) { 140 // Exists 141 return true; 142 } 143 144 if (preference.getOrder() == DEFAULT_ORDER) { 145 if (mOrderingAsAdded) { 146 preference.setOrder(mCurrentPreferenceOrder++); 147 } 148 149 if (preference instanceof PreferenceGroup) { 150 // TODO: fix (method is called tail recursively when inflating, 151 // so we won't end up properly passing this flag down to children 152 ((PreferenceGroup)preference).setOrderingAsAdded(mOrderingAsAdded); 153 } 154 } 155 156 int insertionIndex = Collections.binarySearch(mPreferenceList, preference); 157 if (insertionIndex < 0) { 158 insertionIndex = insertionIndex * -1 - 1; 159 } 160 161 if (!onPrepareAddPreference(preference)) { 162 return false; 163 } 164 165 synchronized(this) { 166 mPreferenceList.add(insertionIndex, preference); 167 } 168 169 preference.onAttachedToHierarchy(getPreferenceManager()); 170 171 if (mAttachedToHierarchy) { 172 preference.onAttached(); 173 } 174 175 notifyHierarchyChanged(); 176 177 return true; 178 } 179 180 /** 181 * Removes a {@link Preference} from this group. 182 * 183 * @param preference The preference to remove. 184 * @return Whether the preference was found and removed. 185 */ 186 public boolean removePreference(Preference preference) { 187 final boolean returnValue = removePreferenceInt(preference); 188 notifyHierarchyChanged(); 189 return returnValue; 190 } 191 192 private boolean removePreferenceInt(Preference preference) { 193 synchronized(this) { 194 preference.onPrepareForRemoval(); 195 return mPreferenceList.remove(preference); 196 } 197 } 198 199 /** 200 * Removes all {@link Preference Preferences} from this group. 201 */ 202 public void removeAll() { 203 synchronized(this) { 204 List<Preference> preferenceList = mPreferenceList; 205 for (int i = preferenceList.size() - 1; i >= 0; i--) { 206 removePreferenceInt(preferenceList.get(0)); 207 } 208 } 209 notifyHierarchyChanged(); 210 } 211 212 /** 213 * Prepares a {@link Preference} to be added to the group. 214 * 215 * @param preference The preference to add. 216 * @return Whether to allow adding the preference (true), or not (false). 217 */ 218 protected boolean onPrepareAddPreference(Preference preference) { 219 preference.onParentChanged(this, shouldDisableDependents()); 220 return true; 221 } 222 223 /** 224 * Finds a {@link Preference} based on its key. If two {@link Preference} 225 * share the same key (not recommended), the first to appear will be 226 * returned (to retrieve the other preference with the same key, call this 227 * method on the first preference). If this preference has the key, it will 228 * not be returned. 229 * <p> 230 * This will recursively search for the preference into children that are 231 * also {@link PreferenceGroup PreferenceGroups}. 232 * 233 * @param key The key of the preference to retrieve. 234 * @return The {@link Preference} with the key, or null. 235 */ 236 public Preference findPreference(CharSequence key) { 237 if (TextUtils.equals(getKey(), key)) { 238 return this; 239 } 240 final int preferenceCount = getPreferenceCount(); 241 for (int i = 0; i < preferenceCount; i++) { 242 final Preference preference = getPreference(i); 243 final String curKey = preference.getKey(); 244 245 if (curKey != null && curKey.equals(key)) { 246 return preference; 247 } 248 249 if (preference instanceof PreferenceGroup) { 250 final Preference returnedPreference = ((PreferenceGroup)preference) 251 .findPreference(key); 252 if (returnedPreference != null) { 253 return returnedPreference; 254 } 255 } 256 } 257 258 return null; 259 } 260 261 /** 262 * Whether this preference group should be shown on the same screen as its 263 * contained preferences. 264 * 265 * @return True if the contained preferences should be shown on the same 266 * screen as this preference. 267 */ 268 protected boolean isOnSameScreenAsChildren() { 269 return true; 270 } 271 272 @Override 273 public void onAttached() { 274 super.onAttached(); 275 276 // Mark as attached so if a preference is later added to this group, we 277 // can tell it we are already attached 278 mAttachedToHierarchy = true; 279 280 // Dispatch to all contained preferences 281 final int preferenceCount = getPreferenceCount(); 282 for (int i = 0; i < preferenceCount; i++) { 283 getPreference(i).onAttached(); 284 } 285 } 286 287 @Override 288 protected void onPrepareForRemoval() { 289 super.onPrepareForRemoval(); 290 291 // We won't be attached to the activity anymore 292 mAttachedToHierarchy = false; 293 } 294 295 @Override 296 public void notifyDependencyChange(boolean disableDependents) { 297 super.notifyDependencyChange(disableDependents); 298 299 // Child preferences have an implicit dependency on their containing 300 // group. Dispatch dependency change to all contained preferences. 301 final int preferenceCount = getPreferenceCount(); 302 for (int i = 0; i < preferenceCount; i++) { 303 getPreference(i).onParentChanged(this, disableDependents); 304 } 305 } 306 307 void sortPreferences() { 308 synchronized (this) { 309 Collections.sort(mPreferenceList); 310 } 311 } 312 313 @Override 314 protected void dispatchSaveInstanceState(Bundle container) { 315 super.dispatchSaveInstanceState(container); 316 317 // Dispatch to all contained preferences 318 final int preferenceCount = getPreferenceCount(); 319 for (int i = 0; i < preferenceCount; i++) { 320 getPreference(i).dispatchSaveInstanceState(container); 321 } 322 } 323 324 @Override 325 protected void dispatchRestoreInstanceState(Bundle container) { 326 super.dispatchRestoreInstanceState(container); 327 328 // Dispatch to all contained preferences 329 final int preferenceCount = getPreferenceCount(); 330 for (int i = 0; i < preferenceCount; i++) { 331 getPreference(i).dispatchRestoreInstanceState(container); 332 } 333 } 334 335} 336