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