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