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