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