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