1 /* 2 * Copyright (C) 2010 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 android.app.Activity; 20 import android.app.Fragment; 21 import android.content.Intent; 22 import android.content.SharedPreferences; 23 import android.content.res.TypedArray; 24 import android.os.Bundle; 25 import android.os.Handler; 26 import android.os.Message; 27 import android.view.KeyEvent; 28 import android.view.LayoutInflater; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.View.OnKeyListener; 32 import android.widget.ListView; 33 34 /** 35 * Shows a hierarchy of {@link Preference} objects as 36 * lists. These preferences will 37 * automatically save to {@link SharedPreferences} as the user interacts with 38 * them. To retrieve an instance of {@link SharedPreferences} that the 39 * preference hierarchy in this fragment will use, call 40 * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)} 41 * with a context in the same package as this fragment. 42 * <p> 43 * Furthermore, the preferences shown will follow the visual style of system 44 * preferences. It is easy to create a hierarchy of preferences (that can be 45 * shown on multiple screens) via XML. For these reasons, it is recommended to 46 * use this fragment (as a superclass) to deal with preferences in applications. 47 * <p> 48 * A {@link PreferenceScreen} object should be at the top of the preference 49 * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy 50 * denote a screen break--that is the preferences contained within subsequent 51 * {@link PreferenceScreen} should be shown on another screen. The preference 52 * framework handles showing these other screens from the preference hierarchy. 53 * <p> 54 * The preference hierarchy can be formed in multiple ways: 55 * <li> From an XML file specifying the hierarchy 56 * <li> From different {@link Activity Activities} that each specify its own 57 * preferences in an XML file via {@link Activity} meta-data 58 * <li> From an object hierarchy rooted with {@link PreferenceScreen} 59 * <p> 60 * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The 61 * root element should be a {@link PreferenceScreen}. Subsequent elements can point 62 * to actual {@link Preference} subclasses. As mentioned above, subsequent 63 * {@link PreferenceScreen} in the hierarchy will result in the screen break. 64 * <p> 65 * To specify an {@link Intent} to query {@link Activity Activities} that each 66 * have preferences, use {@link #addPreferencesFromIntent}. Each 67 * {@link Activity} can specify meta-data in the manifest (via the key 68 * {@link PreferenceManager#METADATA_KEY_PREFERENCES}) that points to an XML 69 * resource. These XML resources will be inflated into a single preference 70 * hierarchy and shown by this fragment. 71 * <p> 72 * To specify an object hierarchy rooted with {@link PreferenceScreen}, use 73 * {@link #setPreferenceScreen(PreferenceScreen)}. 74 * <p> 75 * As a convenience, this fragment implements a click listener for any 76 * preference in the current hierarchy, see 77 * {@link #onPreferenceTreeClick(PreferenceScreen, Preference)}. 78 * 79 * <div class="special reference"> 80 * <h3>Developer Guides</h3> 81 * <p>For information about using {@code PreferenceFragment}, 82 * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a> 83 * guide.</p> 84 * </div> 85 * 86 * <a name="SampleCode"></a> 87 * <h3>Sample Code</h3> 88 * 89 * <p>The following sample code shows a simple preference fragment that is 90 * populated from a resource. The resource it loads is:</p> 91 * 92 * {@sample development/samples/ApiDemos/res/xml/preferences.xml preferences} 93 * 94 * <p>The fragment implementation itself simply populates the preferences 95 * when created. Note that the preferences framework takes care of loading 96 * the current values out of the app preferences and writing them when changed:</p> 97 * 98 * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/FragmentPreferences.java 99 * fragment} 100 * 101 * @see Preference 102 * @see PreferenceScreen 103 */ 104 public abstract class PreferenceFragment extends Fragment implements 105 PreferenceManager.OnPreferenceTreeClickListener { 106 107 private static final String PREFERENCES_TAG = "android:preferences"; 108 109 private PreferenceManager mPreferenceManager; 110 private ListView mList; 111 private boolean mHavePrefs; 112 private boolean mInitDone; 113 114 private int mLayoutResId = com.android.internal.R.layout.preference_list_fragment; 115 116 /** 117 * The starting request code given out to preference framework. 118 */ 119 private static final int FIRST_REQUEST_CODE = 100; 120 121 private static final int MSG_BIND_PREFERENCES = 1; 122 private Handler mHandler = new Handler() { 123 @Override 124 public void handleMessage(Message msg) { 125 switch (msg.what) { 126 127 case MSG_BIND_PREFERENCES: 128 bindPreferences(); 129 break; 130 } 131 } 132 }; 133 134 final private Runnable mRequestFocus = new Runnable() { 135 public void run() { 136 mList.focusableViewAvailable(mList); 137 } 138 }; 139 140 /** 141 * Interface that PreferenceFragment's containing activity should 142 * implement to be able to process preference items that wish to 143 * switch to a new fragment. 144 */ 145 public interface OnPreferenceStartFragmentCallback { 146 /** 147 * Called when the user has clicked on a Preference that has 148 * a fragment class name associated with it. The implementation 149 * to should instantiate and switch to an instance of the given 150 * fragment. 151 */ 152 boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref); 153 } 154 155 @Override 156 public void onCreate(Bundle savedInstanceState) { 157 super.onCreate(savedInstanceState); 158 mPreferenceManager = new PreferenceManager(getActivity(), FIRST_REQUEST_CODE); 159 mPreferenceManager.setFragment(this); 160 } 161 162 @Override 163 public View onCreateView(LayoutInflater inflater, ViewGroup container, 164 Bundle savedInstanceState) { 165 166 TypedArray a = getActivity().obtainStyledAttributes(null, 167 com.android.internal.R.styleable.PreferenceFragment, 168 com.android.internal.R.attr.preferenceFragmentStyle, 169 0); 170 171 mLayoutResId = a.getResourceId(com.android.internal.R.styleable.PreferenceFragment_layout, 172 mLayoutResId); 173 174 a.recycle(); 175 176 return inflater.inflate(mLayoutResId, container, false); 177 } 178 179 @Override 180 public void onActivityCreated(Bundle savedInstanceState) { 181 super.onActivityCreated(savedInstanceState); 182 183 if (mHavePrefs) { 184 bindPreferences(); 185 } 186 187 mInitDone = true; 188 189 if (savedInstanceState != null) { 190 Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG); 191 if (container != null) { 192 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 193 if (preferenceScreen != null) { 194 preferenceScreen.restoreHierarchyState(container); 195 } 196 } 197 } 198 } 199 200 @Override 201 public void onStart() { 202 super.onStart(); 203 mPreferenceManager.setOnPreferenceTreeClickListener(this); 204 } 205 206 @Override 207 public void onStop() { 208 super.onStop(); 209 mPreferenceManager.dispatchActivityStop(); 210 mPreferenceManager.setOnPreferenceTreeClickListener(null); 211 } 212 213 @Override 214 public void onDestroyView() { 215 mList = null; 216 mHandler.removeCallbacks(mRequestFocus); 217 mHandler.removeMessages(MSG_BIND_PREFERENCES); 218 super.onDestroyView(); 219 } 220 221 @Override 222 public void onDestroy() { 223 super.onDestroy(); 224 mPreferenceManager.dispatchActivityDestroy(); 225 } 226 227 @Override 228 public void onSaveInstanceState(Bundle outState) { 229 super.onSaveInstanceState(outState); 230 231 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 232 if (preferenceScreen != null) { 233 Bundle container = new Bundle(); 234 preferenceScreen.saveHierarchyState(container); 235 outState.putBundle(PREFERENCES_TAG, container); 236 } 237 } 238 239 @Override 240 public void onActivityResult(int requestCode, int resultCode, Intent data) { 241 super.onActivityResult(requestCode, resultCode, data); 242 243 mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data); 244 } 245 246 /** 247 * Returns the {@link PreferenceManager} used by this fragment. 248 * @return The {@link PreferenceManager}. 249 */ 250 public PreferenceManager getPreferenceManager() { 251 return mPreferenceManager; 252 } 253 254 /** 255 * Sets the root of the preference hierarchy that this fragment is showing. 256 * 257 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. 258 */ 259 public void setPreferenceScreen(PreferenceScreen preferenceScreen) { 260 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) { 261 onUnbindPreferences(); 262 mHavePrefs = true; 263 if (mInitDone) { 264 postBindPreferences(); 265 } 266 } 267 } 268 269 /** 270 * Gets the root of the preference hierarchy that this fragment is showing. 271 * 272 * @return The {@link PreferenceScreen} that is the root of the preference 273 * hierarchy. 274 */ 275 public PreferenceScreen getPreferenceScreen() { 276 return mPreferenceManager.getPreferenceScreen(); 277 } 278 279 /** 280 * Adds preferences from activities that match the given {@link Intent}. 281 * 282 * @param intent The {@link Intent} to query activities. 283 */ 284 public void addPreferencesFromIntent(Intent intent) { 285 requirePreferenceManager(); 286 287 setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen())); 288 } 289 290 /** 291 * Inflates the given XML resource and adds the preference hierarchy to the current 292 * preference hierarchy. 293 * 294 * @param preferencesResId The XML resource ID to inflate. 295 */ 296 public void addPreferencesFromResource(int preferencesResId) { 297 requirePreferenceManager(); 298 299 setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(), 300 preferencesResId, getPreferenceScreen())); 301 } 302 303 /** 304 * {@inheritDoc} 305 */ 306 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, 307 Preference preference) { 308 if (preference.getFragment() != null && 309 getActivity() instanceof OnPreferenceStartFragmentCallback) { 310 return ((OnPreferenceStartFragmentCallback)getActivity()).onPreferenceStartFragment( 311 this, preference); 312 } 313 return false; 314 } 315 316 /** 317 * Finds a {@link Preference} based on its key. 318 * 319 * @param key The key of the preference to retrieve. 320 * @return The {@link Preference} with the key, or null. 321 * @see PreferenceGroup#findPreference(CharSequence) 322 */ 323 public Preference findPreference(CharSequence key) { 324 if (mPreferenceManager == null) { 325 return null; 326 } 327 return mPreferenceManager.findPreference(key); 328 } 329 330 private void requirePreferenceManager() { 331 if (mPreferenceManager == null) { 332 throw new RuntimeException("This should be called after super.onCreate."); 333 } 334 } 335 336 private void postBindPreferences() { 337 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return; 338 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget(); 339 } 340 341 private void bindPreferences() { 342 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 343 if (preferenceScreen != null) { 344 preferenceScreen.bind(getListView()); 345 } 346 onBindPreferences(); 347 } 348 349 /** @hide */ 350 protected void onBindPreferences() { 351 } 352 353 /** @hide */ 354 protected void onUnbindPreferences() { 355 } 356 357 /** @hide */ 358 public ListView getListView() { 359 ensureList(); 360 return mList; 361 } 362 363 /** @hide */ 364 public boolean hasListView() { 365 if (mList != null) { 366 return true; 367 } 368 View root = getView(); 369 if (root == null) { 370 return false; 371 } 372 View rawListView = root.findViewById(android.R.id.list); 373 if (!(rawListView instanceof ListView)) { 374 return false; 375 } 376 mList = (ListView)rawListView; 377 if (mList == null) { 378 return false; 379 } 380 return true; 381 } 382 383 private void ensureList() { 384 if (mList != null) { 385 return; 386 } 387 View root = getView(); 388 if (root == null) { 389 throw new IllegalStateException("Content view not yet created"); 390 } 391 View rawListView = root.findViewById(android.R.id.list); 392 if (!(rawListView instanceof ListView)) { 393 throw new RuntimeException( 394 "Content has view with id attribute 'android.R.id.list' " 395 + "that is not a ListView class"); 396 } 397 mList = (ListView)rawListView; 398 if (mList == null) { 399 throw new RuntimeException( 400 "Your content must have a ListView whose id attribute is " + 401 "'android.R.id.list'"); 402 } 403 mList.setOnKeyListener(mListOnKeyListener); 404 mHandler.post(mRequestFocus); 405 } 406 407 private OnKeyListener mListOnKeyListener = new OnKeyListener() { 408 409 @Override 410 public boolean onKey(View v, int keyCode, KeyEvent event) { 411 Object selectedItem = mList.getSelectedItem(); 412 if (selectedItem instanceof Preference) { 413 View selectedView = mList.getSelectedView(); 414 return ((Preference)selectedItem).onKey( 415 selectedView, keyCode, event); 416 } 417 return false; 418 } 419 420 }; 421 } 422