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