1 /* 2 * Copyright (C) 2007 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.animation.LayoutTransition; 20 import android.annotation.Nullable; 21 import android.annotation.StringRes; 22 import android.annotation.XmlRes; 23 import android.app.Fragment; 24 import android.app.FragmentBreadCrumbs; 25 import android.app.FragmentManager; 26 import android.app.FragmentTransaction; 27 import android.app.ListActivity; 28 import android.compat.annotation.UnsupportedAppUsage; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.res.Resources; 32 import android.content.res.TypedArray; 33 import android.content.res.XmlResourceParser; 34 import android.os.Build; 35 import android.os.Bundle; 36 import android.os.Handler; 37 import android.os.Message; 38 import android.os.Parcel; 39 import android.os.Parcelable; 40 import android.text.TextUtils; 41 import android.util.AttributeSet; 42 import android.util.TypedValue; 43 import android.util.Xml; 44 import android.view.LayoutInflater; 45 import android.view.MenuItem; 46 import android.view.View; 47 import android.view.View.OnClickListener; 48 import android.view.ViewGroup; 49 import android.widget.AbsListView; 50 import android.widget.ArrayAdapter; 51 import android.widget.BaseAdapter; 52 import android.widget.Button; 53 import android.widget.FrameLayout; 54 import android.widget.ImageView; 55 import android.widget.ListView; 56 import android.widget.TextView; 57 58 import com.android.internal.util.XmlUtils; 59 60 import org.xmlpull.v1.XmlPullParser; 61 import org.xmlpull.v1.XmlPullParserException; 62 63 import java.io.IOException; 64 import java.util.ArrayList; 65 import java.util.List; 66 67 /** 68 * This is the base class for an activity to show a hierarchy of preferences 69 * to the user. Prior to {@link android.os.Build.VERSION_CODES#HONEYCOMB} 70 * this class only allowed the display of a single set of preference; this 71 * functionality should now be found in the new {@link PreferenceFragment} 72 * class. If you are using PreferenceActivity in its old mode, the documentation 73 * there applies to the deprecated APIs here. 74 * 75 * <p>This activity shows one or more headers of preferences, each of which 76 * is associated with a {@link PreferenceFragment} to display the preferences 77 * of that header. The actual layout and display of these associations can 78 * however vary; currently there are two major approaches it may take: 79 * 80 * <ul> 81 * <li>On a small screen it may display only the headers as a single list when first launched. 82 * Selecting one of the header items will only show the PreferenceFragment of that header (on 83 * Android N and lower a new Activity is launched). 84 * <li>On a large screen it may display both the headers and current PreferenceFragment together as 85 * panes. Selecting a header item switches to showing the correct PreferenceFragment for that item. 86 * </ul> 87 * 88 * <p>Subclasses of PreferenceActivity should implement 89 * {@link #onBuildHeaders} to populate the header list with the desired 90 * items. Doing this implicitly switches the class into its new "headers 91 * + fragments" mode rather than the old style of just showing a single 92 * preferences list. 93 * 94 * <div class="special reference"> 95 * <h3>Developer Guides</h3> 96 * <p>For information about using {@code PreferenceActivity}, 97 * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a> 98 * guide.</p> 99 * </div> 100 * 101 * @deprecated Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 102 * <a href="{@docRoot}reference/androidx/preference/package-summary.html"> 103 * Preference Library</a> for consistent behavior across all devices. For more information on 104 * using the AndroidX Preference Library see 105 * <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>. 106 */ 107 @Deprecated 108 public abstract class PreferenceActivity extends ListActivity implements 109 PreferenceManager.OnPreferenceTreeClickListener, 110 PreferenceFragment.OnPreferenceStartFragmentCallback { 111 112 private static final String TAG = "PreferenceActivity"; 113 114 // Constants for state save/restore 115 private static final String HEADERS_TAG = ":android:headers"; 116 private static final String CUR_HEADER_TAG = ":android:cur_header"; 117 private static final String PREFERENCES_TAG = ":android:preferences"; 118 119 /** 120 * When starting this activity, the invoking Intent can contain this extra 121 * string to specify which fragment should be initially displayed. 122 * <p/>Starting from Key Lime Pie, when this argument is passed in, the PreferenceActivity 123 * will call isValidFragment() to confirm that the fragment class name is valid for this 124 * activity. 125 */ 126 public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment"; 127 128 /** 129 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 130 * this extra can also be specified to supply a Bundle of arguments to pass 131 * to that fragment when it is instantiated during the initial creation 132 * of PreferenceActivity. 133 */ 134 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args"; 135 136 /** 137 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 138 * this extra can also be specify to supply the title to be shown for 139 * that fragment. 140 */ 141 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":android:show_fragment_title"; 142 143 /** 144 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 145 * this extra can also be specify to supply the short title to be shown for 146 * that fragment. 147 */ 148 public static final String EXTRA_SHOW_FRAGMENT_SHORT_TITLE 149 = ":android:show_fragment_short_title"; 150 151 /** 152 * When starting this activity, the invoking Intent can contain this extra 153 * boolean that the header list should not be displayed. This is most often 154 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch 155 * the activity to display a specific fragment that the user has navigated 156 * to. 157 */ 158 public static final String EXTRA_NO_HEADERS = ":android:no_headers"; 159 160 private static final String BACK_STACK_PREFS = ":android:prefs"; 161 162 // extras that allow any preference activity to be launched as part of a wizard 163 164 // show Back and Next buttons? takes boolean parameter 165 // Back will then return RESULT_CANCELED and Next RESULT_OK 166 private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar"; 167 168 // add a Skip button? 169 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip"; 170 171 // specify custom text for the Back or Next buttons, or cause a button to not appear 172 // at all by setting it to null 173 private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text"; 174 private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text"; 175 176 // --- State for new mode when showing a list of headers + prefs fragment 177 178 private final ArrayList<Header> mHeaders = new ArrayList<Header>(); 179 180 private FrameLayout mListFooter; 181 182 @UnsupportedAppUsage 183 private ViewGroup mPrefsContainer; 184 185 // Backup of the original activity title. This is used when navigating back to the headers list 186 // in onBackPress to restore the title. 187 private CharSequence mActivityTitle; 188 189 // Null if in legacy mode. 190 private ViewGroup mHeadersContainer; 191 192 private FragmentBreadCrumbs mFragmentBreadCrumbs; 193 194 private boolean mSinglePane; 195 196 private Header mCurHeader; 197 198 // --- State for old mode when showing a single preference list 199 200 @UnsupportedAppUsage 201 private PreferenceManager mPreferenceManager; 202 203 private Bundle mSavedInstanceState; 204 205 // --- Common state 206 207 private Button mNextButton; 208 209 private int mPreferenceHeaderItemResId = 0; 210 private boolean mPreferenceHeaderRemoveEmptyIcon = false; 211 212 /** 213 * The starting request code given out to preference framework. 214 */ 215 private static final int FIRST_REQUEST_CODE = 100; 216 217 private static final int MSG_BIND_PREFERENCES = 1; 218 private static final int MSG_BUILD_HEADERS = 2; 219 private Handler mHandler = new Handler() { 220 @Override 221 public void handleMessage(Message msg) { 222 switch (msg.what) { 223 case MSG_BIND_PREFERENCES: { 224 bindPreferences(); 225 } break; 226 case MSG_BUILD_HEADERS: { 227 ArrayList<Header> oldHeaders = new ArrayList<Header>(mHeaders); 228 mHeaders.clear(); 229 onBuildHeaders(mHeaders); 230 if (mAdapter instanceof BaseAdapter) { 231 ((BaseAdapter) mAdapter).notifyDataSetChanged(); 232 } 233 Header header = onGetNewHeader(); 234 if (header != null && header.fragment != null) { 235 Header mappedHeader = findBestMatchingHeader(header, oldHeaders); 236 if (mappedHeader == null || mCurHeader != mappedHeader) { 237 switchToHeader(header); 238 } 239 } else if (mCurHeader != null) { 240 Header mappedHeader = findBestMatchingHeader(mCurHeader, mHeaders); 241 if (mappedHeader != null) { 242 setSelectedHeader(mappedHeader); 243 } 244 } 245 } break; 246 } 247 } 248 }; 249 250 private static class HeaderAdapter extends ArrayAdapter<Header> { 251 private static class HeaderViewHolder { 252 ImageView icon; 253 TextView title; 254 TextView summary; 255 } 256 257 private LayoutInflater mInflater; 258 private int mLayoutResId; 259 private boolean mRemoveIconIfEmpty; 260 HeaderAdapter(Context context, List<Header> objects, int layoutResId, boolean removeIconBehavior)261 public HeaderAdapter(Context context, List<Header> objects, int layoutResId, 262 boolean removeIconBehavior) { 263 super(context, 0, objects); 264 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 265 mLayoutResId = layoutResId; 266 mRemoveIconIfEmpty = removeIconBehavior; 267 } 268 269 @Override getView(int position, View convertView, ViewGroup parent)270 public View getView(int position, View convertView, ViewGroup parent) { 271 HeaderViewHolder holder; 272 View view; 273 274 if (convertView == null) { 275 view = mInflater.inflate(mLayoutResId, parent, false); 276 holder = new HeaderViewHolder(); 277 holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon); 278 holder.title = (TextView) view.findViewById(com.android.internal.R.id.title); 279 holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary); 280 view.setTag(holder); 281 } else { 282 view = convertView; 283 holder = (HeaderViewHolder) view.getTag(); 284 } 285 286 // All view fields must be updated every time, because the view may be recycled 287 Header header = getItem(position); 288 if (mRemoveIconIfEmpty) { 289 if (header.iconRes == 0) { 290 holder.icon.setVisibility(View.GONE); 291 } else { 292 holder.icon.setVisibility(View.VISIBLE); 293 holder.icon.setImageResource(header.iconRes); 294 } 295 } else { 296 holder.icon.setImageResource(header.iconRes); 297 } 298 holder.title.setText(header.getTitle(getContext().getResources())); 299 CharSequence summary = header.getSummary(getContext().getResources()); 300 if (!TextUtils.isEmpty(summary)) { 301 holder.summary.setVisibility(View.VISIBLE); 302 holder.summary.setText(summary); 303 } else { 304 holder.summary.setVisibility(View.GONE); 305 } 306 307 return view; 308 } 309 } 310 311 /** 312 * Default value for {@link Header#id Header.id} indicating that no 313 * identifier value is set. All other values (including those below -1) 314 * are valid. 315 */ 316 public static final long HEADER_ID_UNDEFINED = -1; 317 318 /** 319 * Description of a single Header item that the user can select. 320 * 321 * @deprecated Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 322 * <a href="{@docRoot}reference/androidx/preference/package-summary.html"> 323 * Preference Library</a> for consistent behavior across all devices. 324 * For more information on using the AndroidX Preference Library see 325 * <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>. 326 */ 327 @Deprecated 328 public static final class Header implements Parcelable { 329 /** 330 * Identifier for this header, to correlate with a new list when 331 * it is updated. The default value is 332 * {@link PreferenceActivity#HEADER_ID_UNDEFINED}, meaning no id. 333 * @attr ref android.R.styleable#PreferenceHeader_id 334 */ 335 public long id = HEADER_ID_UNDEFINED; 336 337 /** 338 * Resource ID of title of the header that is shown to the user. 339 * @attr ref android.R.styleable#PreferenceHeader_title 340 */ 341 @StringRes 342 public int titleRes; 343 344 /** 345 * Title of the header that is shown to the user. 346 * @attr ref android.R.styleable#PreferenceHeader_title 347 */ 348 public CharSequence title; 349 350 /** 351 * Resource ID of optional summary describing what this header controls. 352 * @attr ref android.R.styleable#PreferenceHeader_summary 353 */ 354 @StringRes 355 public int summaryRes; 356 357 /** 358 * Optional summary describing what this header controls. 359 * @attr ref android.R.styleable#PreferenceHeader_summary 360 */ 361 public CharSequence summary; 362 363 /** 364 * Resource ID of optional text to show as the title in the bread crumb. 365 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle 366 */ 367 @StringRes 368 public int breadCrumbTitleRes; 369 370 /** 371 * Optional text to show as the title in the bread crumb. 372 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle 373 */ 374 public CharSequence breadCrumbTitle; 375 376 /** 377 * Resource ID of optional text to show as the short title in the bread crumb. 378 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle 379 */ 380 @StringRes 381 public int breadCrumbShortTitleRes; 382 383 /** 384 * Optional text to show as the short title in the bread crumb. 385 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle 386 */ 387 public CharSequence breadCrumbShortTitle; 388 389 /** 390 * Optional icon resource to show for this header. 391 * @attr ref android.R.styleable#PreferenceHeader_icon 392 */ 393 public int iconRes; 394 395 /** 396 * Full class name of the fragment to display when this header is 397 * selected. 398 * @attr ref android.R.styleable#PreferenceHeader_fragment 399 */ 400 public String fragment; 401 402 /** 403 * Optional arguments to supply to the fragment when it is 404 * instantiated. 405 */ 406 public Bundle fragmentArguments; 407 408 /** 409 * Intent to launch when the preference is selected. 410 */ 411 public Intent intent; 412 413 /** 414 * Optional additional data for use by subclasses of PreferenceActivity. 415 */ 416 public Bundle extras; 417 Header()418 public Header() { 419 // Empty 420 } 421 422 /** 423 * Return the currently set title. If {@link #titleRes} is set, 424 * this resource is loaded from <var>res</var> and returned. Otherwise 425 * {@link #title} is returned. 426 */ getTitle(Resources res)427 public CharSequence getTitle(Resources res) { 428 if (titleRes != 0) { 429 return res.getText(titleRes); 430 } 431 return title; 432 } 433 434 /** 435 * Return the currently set summary. If {@link #summaryRes} is set, 436 * this resource is loaded from <var>res</var> and returned. Otherwise 437 * {@link #summary} is returned. 438 */ getSummary(Resources res)439 public CharSequence getSummary(Resources res) { 440 if (summaryRes != 0) { 441 return res.getText(summaryRes); 442 } 443 return summary; 444 } 445 446 /** 447 * Return the currently set bread crumb title. If {@link #breadCrumbTitleRes} is set, 448 * this resource is loaded from <var>res</var> and returned. Otherwise 449 * {@link #breadCrumbTitle} is returned. 450 */ getBreadCrumbTitle(Resources res)451 public CharSequence getBreadCrumbTitle(Resources res) { 452 if (breadCrumbTitleRes != 0) { 453 return res.getText(breadCrumbTitleRes); 454 } 455 return breadCrumbTitle; 456 } 457 458 /** 459 * Return the currently set bread crumb short title. If 460 * {@link #breadCrumbShortTitleRes} is set, 461 * this resource is loaded from <var>res</var> and returned. Otherwise 462 * {@link #breadCrumbShortTitle} is returned. 463 */ getBreadCrumbShortTitle(Resources res)464 public CharSequence getBreadCrumbShortTitle(Resources res) { 465 if (breadCrumbShortTitleRes != 0) { 466 return res.getText(breadCrumbShortTitleRes); 467 } 468 return breadCrumbShortTitle; 469 } 470 471 @Override describeContents()472 public int describeContents() { 473 return 0; 474 } 475 476 @Override writeToParcel(Parcel dest, int flags)477 public void writeToParcel(Parcel dest, int flags) { 478 dest.writeLong(id); 479 dest.writeInt(titleRes); 480 TextUtils.writeToParcel(title, dest, flags); 481 dest.writeInt(summaryRes); 482 TextUtils.writeToParcel(summary, dest, flags); 483 dest.writeInt(breadCrumbTitleRes); 484 TextUtils.writeToParcel(breadCrumbTitle, dest, flags); 485 dest.writeInt(breadCrumbShortTitleRes); 486 TextUtils.writeToParcel(breadCrumbShortTitle, dest, flags); 487 dest.writeInt(iconRes); 488 dest.writeString(fragment); 489 dest.writeBundle(fragmentArguments); 490 if (intent != null) { 491 dest.writeInt(1); 492 intent.writeToParcel(dest, flags); 493 } else { 494 dest.writeInt(0); 495 } 496 dest.writeBundle(extras); 497 } 498 readFromParcel(Parcel in)499 public void readFromParcel(Parcel in) { 500 id = in.readLong(); 501 titleRes = in.readInt(); 502 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 503 summaryRes = in.readInt(); 504 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 505 breadCrumbTitleRes = in.readInt(); 506 breadCrumbTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 507 breadCrumbShortTitleRes = in.readInt(); 508 breadCrumbShortTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 509 iconRes = in.readInt(); 510 fragment = in.readString(); 511 fragmentArguments = in.readBundle(); 512 if (in.readInt() != 0) { 513 intent = Intent.CREATOR.createFromParcel(in); 514 } 515 extras = in.readBundle(); 516 } 517 Header(Parcel in)518 Header(Parcel in) { 519 readFromParcel(in); 520 } 521 522 public static final @android.annotation.NonNull Creator<Header> CREATOR = new Creator<Header>() { 523 public Header createFromParcel(Parcel source) { 524 return new Header(source); 525 } 526 public Header[] newArray(int size) { 527 return new Header[size]; 528 } 529 }; 530 } 531 532 @Override onOptionsItemSelected(MenuItem item)533 public boolean onOptionsItemSelected(MenuItem item) { 534 if (item.getItemId() == android.R.id.home) { 535 // Override home navigation button to call onBackPressed (b/35152749). 536 onBackPressed(); 537 return true; 538 } 539 return super.onOptionsItemSelected(item); 540 } 541 542 @Override onCreate(@ullable Bundle savedInstanceState)543 protected void onCreate(@Nullable Bundle savedInstanceState) { 544 super.onCreate(savedInstanceState); 545 546 // Theming for the PreferenceActivity layout and for the Preference Header(s) layout 547 TypedArray sa = obtainStyledAttributes(null, 548 com.android.internal.R.styleable.PreferenceActivity, 549 com.android.internal.R.attr.preferenceActivityStyle, 550 0); 551 552 final int layoutResId = sa.getResourceId( 553 com.android.internal.R.styleable.PreferenceActivity_layout, 554 com.android.internal.R.layout.preference_list_content); 555 556 mPreferenceHeaderItemResId = sa.getResourceId( 557 com.android.internal.R.styleable.PreferenceActivity_headerLayout, 558 com.android.internal.R.layout.preference_header_item); 559 mPreferenceHeaderRemoveEmptyIcon = sa.getBoolean( 560 com.android.internal.R.styleable.PreferenceActivity_headerRemoveIconIfEmpty, 561 false); 562 563 sa.recycle(); 564 565 setContentView(layoutResId); 566 567 mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer); 568 mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs_frame); 569 mHeadersContainer = (ViewGroup) findViewById(com.android.internal.R.id.headers); 570 boolean hidingHeaders = onIsHidingHeaders(); 571 mSinglePane = hidingHeaders || !onIsMultiPane(); 572 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT); 573 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); 574 int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0); 575 int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0); 576 mActivityTitle = getTitle(); 577 578 if (savedInstanceState != null) { 579 // We are restarting from a previous saved state; used that to 580 // initialize, instead of starting fresh. 581 ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG, android.preference.PreferenceActivity.Header.class); 582 if (headers != null) { 583 mHeaders.addAll(headers); 584 int curHeader = savedInstanceState.getInt(CUR_HEADER_TAG, 585 (int) HEADER_ID_UNDEFINED); 586 if (curHeader >= 0 && curHeader < mHeaders.size()) { 587 setSelectedHeader(mHeaders.get(curHeader)); 588 } else if (!mSinglePane && initialFragment == null) { 589 switchToHeader(onGetInitialHeader()); 590 } 591 } else { 592 // This will for instance hide breadcrumbs for single pane. 593 showBreadCrumbs(getTitle(), null); 594 } 595 } else { 596 if (!onIsHidingHeaders()) { 597 onBuildHeaders(mHeaders); 598 } 599 600 if (initialFragment != null) { 601 switchToHeader(initialFragment, initialArguments); 602 } else if (!mSinglePane && mHeaders.size() > 0) { 603 switchToHeader(onGetInitialHeader()); 604 } 605 } 606 607 if (mHeaders.size() > 0) { 608 setListAdapter(new HeaderAdapter(this, mHeaders, mPreferenceHeaderItemResId, 609 mPreferenceHeaderRemoveEmptyIcon)); 610 if (!mSinglePane) { 611 getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); 612 } 613 } 614 615 if (mSinglePane && initialFragment != null && initialTitle != 0) { 616 CharSequence initialTitleStr = getText(initialTitle); 617 CharSequence initialShortTitleStr = initialShortTitle != 0 618 ? getText(initialShortTitle) : null; 619 showBreadCrumbs(initialTitleStr, initialShortTitleStr); 620 } 621 622 if (mHeaders.size() == 0 && initialFragment == null) { 623 // If there are no headers, we are in the old "just show a screen 624 // of preferences" mode. 625 setContentView(com.android.internal.R.layout.preference_list_content_single); 626 mListFooter = (FrameLayout) findViewById(com.android.internal.R.id.list_footer); 627 mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs); 628 mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE); 629 mPreferenceManager.setOnPreferenceTreeClickListener(this); 630 mHeadersContainer = null; 631 } else if (mSinglePane) { 632 // Single-pane so one of the header or prefs containers must be hidden. 633 if (initialFragment != null || mCurHeader != null) { 634 mHeadersContainer.setVisibility(View.GONE); 635 } else { 636 mPrefsContainer.setVisibility(View.GONE); 637 } 638 639 // This animates our manual transitions between headers and prefs panel in single-pane. 640 // It also comes last so we don't animate any initial layout changes done above. 641 ViewGroup container = (ViewGroup) findViewById( 642 com.android.internal.R.id.prefs_container); 643 container.setLayoutTransition(new LayoutTransition()); 644 } else { 645 // Multi-pane 646 if (mHeaders.size() > 0 && mCurHeader != null) { 647 setSelectedHeader(mCurHeader); 648 } 649 } 650 651 // see if we should show Back/Next buttons 652 Intent intent = getIntent(); 653 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) { 654 655 findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE); 656 657 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button); 658 backButton.setOnClickListener(new OnClickListener() { 659 public void onClick(View v) { 660 setResult(RESULT_CANCELED); 661 finish(); 662 } 663 }); 664 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button); 665 skipButton.setOnClickListener(new OnClickListener() { 666 public void onClick(View v) { 667 setResult(RESULT_OK); 668 finish(); 669 } 670 }); 671 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button); 672 mNextButton.setOnClickListener(new OnClickListener() { 673 public void onClick(View v) { 674 setResult(RESULT_OK); 675 finish(); 676 } 677 }); 678 679 // set our various button parameters 680 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) { 681 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT); 682 if (TextUtils.isEmpty(buttonText)) { 683 mNextButton.setVisibility(View.GONE); 684 } 685 else { 686 mNextButton.setText(buttonText); 687 } 688 } 689 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) { 690 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT); 691 if (TextUtils.isEmpty(buttonText)) { 692 backButton.setVisibility(View.GONE); 693 } 694 else { 695 backButton.setText(buttonText); 696 } 697 } 698 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) { 699 skipButton.setVisibility(View.VISIBLE); 700 } 701 } 702 } 703 704 @Override onBackPressed()705 public void onBackPressed() { 706 if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0 707 && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) { 708 mCurHeader = null; 709 710 mPrefsContainer.setVisibility(View.GONE); 711 mHeadersContainer.setVisibility(View.VISIBLE); 712 if (mActivityTitle != null) { 713 showBreadCrumbs(mActivityTitle, null); 714 } 715 getListView().clearChoices(); 716 } else { 717 super.onBackPressed(); 718 } 719 } 720 721 /** 722 * Returns true if this activity is currently showing the header list. 723 */ hasHeaders()724 public boolean hasHeaders() { 725 return mHeadersContainer != null && mHeadersContainer.getVisibility() == View.VISIBLE; 726 } 727 728 /** 729 * Returns the Header list 730 * @hide 731 */ 732 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getHeaders()733 public List<Header> getHeaders() { 734 return mHeaders; 735 } 736 737 /** 738 * Returns true if this activity is showing multiple panes -- the headers 739 * and a preference fragment. 740 */ isMultiPane()741 public boolean isMultiPane() { 742 return !mSinglePane; 743 } 744 745 /** 746 * Called to determine if the activity should run in multi-pane mode. 747 * The default implementation returns true if the screen is large 748 * enough. 749 */ onIsMultiPane()750 public boolean onIsMultiPane() { 751 boolean preferMultiPane = getResources().getBoolean( 752 com.android.internal.R.bool.preferences_prefer_dual_pane); 753 return preferMultiPane; 754 } 755 756 /** 757 * Called to determine whether the header list should be hidden. 758 * The default implementation returns the 759 * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied. 760 * This is set to false, for example, when the activity is being re-launched 761 * to show a particular preference activity. 762 */ onIsHidingHeaders()763 public boolean onIsHidingHeaders() { 764 return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false); 765 } 766 767 /** 768 * Called to determine the initial header to be shown. The default 769 * implementation simply returns the fragment of the first header. Note 770 * that the returned Header object does not actually need to exist in 771 * your header list -- whatever its fragment is will simply be used to 772 * show for the initial UI. 773 */ onGetInitialHeader()774 public Header onGetInitialHeader() { 775 for (int i=0; i<mHeaders.size(); i++) { 776 Header h = mHeaders.get(i); 777 if (h.fragment != null) { 778 return h; 779 } 780 } 781 throw new IllegalStateException("Must have at least one header with a fragment"); 782 } 783 784 /** 785 * Called after the header list has been updated ({@link #onBuildHeaders} 786 * has been called and returned due to {@link #invalidateHeaders()}) to 787 * specify the header that should now be selected. The default implementation 788 * returns null to keep whatever header is currently selected. 789 */ onGetNewHeader()790 public Header onGetNewHeader() { 791 return null; 792 } 793 794 /** 795 * Called when the activity needs its list of headers build. By 796 * implementing this and adding at least one item to the list, you 797 * will cause the activity to run in its modern fragment mode. Note 798 * that this function may not always be called; for example, if the 799 * activity has been asked to display a particular fragment without 800 * the header list, there is no need to build the headers. 801 * 802 * <p>Typical implementations will use {@link #loadHeadersFromResource} 803 * to fill in the list from a resource. 804 * 805 * @param target The list in which to place the headers. 806 */ onBuildHeaders(List<Header> target)807 public void onBuildHeaders(List<Header> target) { 808 // Should be overloaded by subclasses 809 } 810 811 /** 812 * Call when you need to change the headers being displayed. Will result 813 * in onBuildHeaders() later being called to retrieve the new list. 814 */ invalidateHeaders()815 public void invalidateHeaders() { 816 if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) { 817 mHandler.sendEmptyMessage(MSG_BUILD_HEADERS); 818 } 819 } 820 821 /** 822 * Parse the given XML file as a header description, adding each 823 * parsed Header into the target list. 824 * 825 * @param resid The XML resource to load and parse. 826 * @param target The list in which the parsed headers should be placed. 827 */ loadHeadersFromResource(@mlRes int resid, List<Header> target)828 public void loadHeadersFromResource(@XmlRes int resid, List<Header> target) { 829 XmlResourceParser parser = null; 830 try { 831 parser = getResources().getXml(resid); 832 AttributeSet attrs = Xml.asAttributeSet(parser); 833 834 int type; 835 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 836 && type != XmlPullParser.START_TAG) { 837 // Parse next until start tag is found 838 } 839 840 String nodeName = parser.getName(); 841 if (!"preference-headers".equals(nodeName)) { 842 throw new RuntimeException( 843 "XML document must start with <preference-headers> tag; found" 844 + nodeName + " at " + parser.getPositionDescription()); 845 } 846 847 Bundle curBundle = null; 848 849 final int outerDepth = parser.getDepth(); 850 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 851 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 852 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 853 continue; 854 } 855 856 nodeName = parser.getName(); 857 if ("header".equals(nodeName)) { 858 Header header = new Header(); 859 860 TypedArray sa = obtainStyledAttributes( 861 attrs, com.android.internal.R.styleable.PreferenceHeader); 862 header.id = sa.getResourceId( 863 com.android.internal.R.styleable.PreferenceHeader_id, 864 (int)HEADER_ID_UNDEFINED); 865 TypedValue tv = sa.peekValue( 866 com.android.internal.R.styleable.PreferenceHeader_title); 867 if (tv != null && tv.type == TypedValue.TYPE_STRING) { 868 if (tv.resourceId != 0) { 869 header.titleRes = tv.resourceId; 870 } else { 871 header.title = tv.string; 872 } 873 } 874 tv = sa.peekValue( 875 com.android.internal.R.styleable.PreferenceHeader_summary); 876 if (tv != null && tv.type == TypedValue.TYPE_STRING) { 877 if (tv.resourceId != 0) { 878 header.summaryRes = tv.resourceId; 879 } else { 880 header.summary = tv.string; 881 } 882 } 883 tv = sa.peekValue( 884 com.android.internal.R.styleable.PreferenceHeader_breadCrumbTitle); 885 if (tv != null && tv.type == TypedValue.TYPE_STRING) { 886 if (tv.resourceId != 0) { 887 header.breadCrumbTitleRes = tv.resourceId; 888 } else { 889 header.breadCrumbTitle = tv.string; 890 } 891 } 892 tv = sa.peekValue( 893 com.android.internal.R.styleable.PreferenceHeader_breadCrumbShortTitle); 894 if (tv != null && tv.type == TypedValue.TYPE_STRING) { 895 if (tv.resourceId != 0) { 896 header.breadCrumbShortTitleRes = tv.resourceId; 897 } else { 898 header.breadCrumbShortTitle = tv.string; 899 } 900 } 901 header.iconRes = sa.getResourceId( 902 com.android.internal.R.styleable.PreferenceHeader_icon, 0); 903 header.fragment = sa.getString( 904 com.android.internal.R.styleable.PreferenceHeader_fragment); 905 sa.recycle(); 906 907 if (curBundle == null) { 908 curBundle = new Bundle(); 909 } 910 911 final int innerDepth = parser.getDepth(); 912 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 913 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { 914 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 915 continue; 916 } 917 918 String innerNodeName = parser.getName(); 919 if (innerNodeName.equals("extra")) { 920 getResources().parseBundleExtra("extra", attrs, curBundle); 921 XmlUtils.skipCurrentTag(parser); 922 923 } else if (innerNodeName.equals("intent")) { 924 header.intent = Intent.parseIntent(getResources(), parser, attrs); 925 926 } else { 927 XmlUtils.skipCurrentTag(parser); 928 } 929 } 930 931 if (curBundle.size() > 0) { 932 header.fragmentArguments = curBundle; 933 curBundle = null; 934 } 935 936 target.add(header); 937 } else { 938 XmlUtils.skipCurrentTag(parser); 939 } 940 } 941 942 } catch (XmlPullParserException e) { 943 throw new RuntimeException("Error parsing headers", e); 944 } catch (IOException e) { 945 throw new RuntimeException("Error parsing headers", e); 946 } finally { 947 if (parser != null) parser.close(); 948 } 949 } 950 951 /** 952 * Subclasses should override this method and verify that the given fragment is a valid type 953 * to be attached to this activity. The default implementation returns <code>true</code> for 954 * apps built for <code>android:targetSdkVersion</code> older than 955 * {@link android.os.Build.VERSION_CODES#KITKAT}. For later versions, it will throw an exception. 956 * @param fragmentName the class name of the Fragment about to be attached to this activity. 957 * @return true if the fragment class name is valid for this Activity and false otherwise. 958 */ isValidFragment(String fragmentName)959 protected boolean isValidFragment(String fragmentName) { 960 if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.KITKAT) { 961 throw new RuntimeException( 962 "Subclasses of PreferenceActivity must override isValidFragment(String)" 963 + " to verify that the Fragment class is valid! " 964 + this.getClass().getName() 965 + " has not checked if fragment " + fragmentName + " is valid."); 966 } else { 967 return true; 968 } 969 } 970 971 /** 972 * Set a footer that should be shown at the bottom of the header list. 973 */ setListFooter(View view)974 public void setListFooter(View view) { 975 mListFooter.removeAllViews(); 976 mListFooter.addView(view, new FrameLayout.LayoutParams( 977 FrameLayout.LayoutParams.MATCH_PARENT, 978 FrameLayout.LayoutParams.WRAP_CONTENT)); 979 } 980 981 @Override onStop()982 protected void onStop() { 983 super.onStop(); 984 985 if (mPreferenceManager != null) { 986 mPreferenceManager.dispatchActivityStop(); 987 } 988 } 989 990 @Override onDestroy()991 protected void onDestroy() { 992 mHandler.removeMessages(MSG_BIND_PREFERENCES); 993 mHandler.removeMessages(MSG_BUILD_HEADERS); 994 super.onDestroy(); 995 996 if (mPreferenceManager != null) { 997 mPreferenceManager.dispatchActivityDestroy(); 998 } 999 } 1000 1001 @Override onSaveInstanceState(Bundle outState)1002 protected void onSaveInstanceState(Bundle outState) { 1003 super.onSaveInstanceState(outState); 1004 1005 if (mHeaders.size() > 0) { 1006 outState.putParcelableArrayList(HEADERS_TAG, mHeaders); 1007 if (mCurHeader != null) { 1008 int index = mHeaders.indexOf(mCurHeader); 1009 if (index >= 0) { 1010 outState.putInt(CUR_HEADER_TAG, index); 1011 } 1012 } 1013 } 1014 1015 if (mPreferenceManager != null) { 1016 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 1017 if (preferenceScreen != null) { 1018 Bundle container = new Bundle(); 1019 preferenceScreen.saveHierarchyState(container); 1020 outState.putBundle(PREFERENCES_TAG, container); 1021 } 1022 } 1023 } 1024 1025 @Override onRestoreInstanceState(Bundle state)1026 protected void onRestoreInstanceState(Bundle state) { 1027 if (mPreferenceManager != null) { 1028 Bundle container = state.getBundle(PREFERENCES_TAG); 1029 if (container != null) { 1030 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 1031 if (preferenceScreen != null) { 1032 preferenceScreen.restoreHierarchyState(container); 1033 mSavedInstanceState = state; 1034 return; 1035 } 1036 } 1037 } 1038 1039 // Only call this if we didn't save the instance state for later. 1040 // If we did save it, it will be restored when we bind the adapter. 1041 super.onRestoreInstanceState(state); 1042 1043 if (!mSinglePane) { 1044 // Multi-pane. 1045 if (mCurHeader != null) { 1046 setSelectedHeader(mCurHeader); 1047 } 1048 } 1049 } 1050 1051 @Override onActivityResult(int requestCode, int resultCode, Intent data)1052 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 1053 super.onActivityResult(requestCode, resultCode, data); 1054 1055 if (mPreferenceManager != null) { 1056 mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data); 1057 } 1058 } 1059 1060 @Override onContentChanged()1061 public void onContentChanged() { 1062 super.onContentChanged(); 1063 1064 if (mPreferenceManager != null) { 1065 postBindPreferences(); 1066 } 1067 } 1068 1069 @Override onListItemClick(ListView l, View v, int position, long id)1070 protected void onListItemClick(ListView l, View v, int position, long id) { 1071 if (!isResumed()) { 1072 return; 1073 } 1074 super.onListItemClick(l, v, position, id); 1075 1076 if (mAdapter != null) { 1077 Object item = mAdapter.getItem(position); 1078 if (item instanceof Header) onHeaderClick((Header) item, position); 1079 } 1080 } 1081 1082 /** 1083 * Called when the user selects an item in the header list. The default 1084 * implementation will call either 1085 * {@link #startWithFragment(String, Bundle, Fragment, int, int, int)} 1086 * or {@link #switchToHeader(Header)} as appropriate. 1087 * 1088 * @param header The header that was selected. 1089 * @param position The header's position in the list. 1090 */ onHeaderClick(Header header, int position)1091 public void onHeaderClick(Header header, int position) { 1092 if (header.fragment != null) { 1093 switchToHeader(header); 1094 } else if (header.intent != null) { 1095 startActivity(header.intent); 1096 } 1097 } 1098 1099 /** 1100 * Called by {@link #startWithFragment(String, Bundle, Fragment, int, int, int)} when 1101 * in single-pane mode, to build an Intent to launch a new activity showing 1102 * the selected fragment. The default implementation constructs an Intent 1103 * that re-launches the current activity with the appropriate arguments to 1104 * display the fragment. 1105 * 1106 * @param fragmentName The name of the fragment to display. 1107 * @param args Optional arguments to supply to the fragment. 1108 * @param titleRes Optional resource ID of title to show for this item. 1109 * @param shortTitleRes Optional resource ID of short title to show for this item. 1110 * @return Returns an Intent that can be launched to display the given 1111 * fragment. 1112 */ onBuildStartFragmentIntent(String fragmentName, Bundle args, @StringRes int titleRes, int shortTitleRes)1113 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, 1114 @StringRes int titleRes, int shortTitleRes) { 1115 Intent intent = new Intent(Intent.ACTION_MAIN); 1116 intent.setClass(this, getClass()); 1117 intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName); 1118 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); 1119 intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, titleRes); 1120 intent.putExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, shortTitleRes); 1121 intent.putExtra(EXTRA_NO_HEADERS, true); 1122 return intent; 1123 } 1124 1125 /** 1126 * Like {@link #startWithFragment(String, Bundle, Fragment, int, int, int)} 1127 * but uses a 0 titleRes. 1128 */ startWithFragment(String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode)1129 public void startWithFragment(String fragmentName, Bundle args, 1130 Fragment resultTo, int resultRequestCode) { 1131 startWithFragment(fragmentName, args, resultTo, resultRequestCode, 0, 0); 1132 } 1133 1134 /** 1135 * Start a new instance of this activity, showing only the given 1136 * preference fragment. When launched in this mode, the header list 1137 * will be hidden and the given preference fragment will be instantiated 1138 * and fill the entire activity. 1139 * 1140 * @param fragmentName The name of the fragment to display. 1141 * @param args Optional arguments to supply to the fragment. 1142 * @param resultTo Option fragment that should receive the result of 1143 * the activity launch. 1144 * @param resultRequestCode If resultTo is non-null, this is the request 1145 * code in which to report the result. 1146 * @param titleRes Resource ID of string to display for the title of 1147 * this set of preferences. 1148 * @param shortTitleRes Resource ID of string to display for the short title of 1149 * this set of preferences. 1150 */ startWithFragment(String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode, @StringRes int titleRes, @StringRes int shortTitleRes)1151 public void startWithFragment(String fragmentName, Bundle args, 1152 Fragment resultTo, int resultRequestCode, @StringRes int titleRes, 1153 @StringRes int shortTitleRes) { 1154 Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes); 1155 if (resultTo == null) { 1156 startActivity(intent); 1157 } else { 1158 resultTo.startActivityForResult(intent, resultRequestCode); 1159 } 1160 } 1161 1162 /** 1163 * Change the base title of the bread crumbs for the current preferences. 1164 * This will normally be called for you. See 1165 * {@link android.app.FragmentBreadCrumbs} for more information. 1166 */ showBreadCrumbs(CharSequence title, CharSequence shortTitle)1167 public void showBreadCrumbs(CharSequence title, CharSequence shortTitle) { 1168 if (mFragmentBreadCrumbs == null) { 1169 View crumbs = findViewById(android.R.id.title); 1170 // For screens with a different kind of title, don't create breadcrumbs. 1171 try { 1172 mFragmentBreadCrumbs = (FragmentBreadCrumbs)crumbs; 1173 } catch (ClassCastException e) { 1174 setTitle(title); 1175 return; 1176 } 1177 if (mFragmentBreadCrumbs == null) { 1178 if (title != null) { 1179 setTitle(title); 1180 } 1181 return; 1182 } 1183 if (mSinglePane) { 1184 mFragmentBreadCrumbs.setVisibility(View.GONE); 1185 // Hide the breadcrumb section completely for single-pane 1186 View bcSection = findViewById(com.android.internal.R.id.breadcrumb_section); 1187 if (bcSection != null) bcSection.setVisibility(View.GONE); 1188 setTitle(title); 1189 } 1190 mFragmentBreadCrumbs.setMaxVisible(2); 1191 mFragmentBreadCrumbs.setActivity(this); 1192 } 1193 if (mFragmentBreadCrumbs.getVisibility() != View.VISIBLE) { 1194 setTitle(title); 1195 } else { 1196 mFragmentBreadCrumbs.setTitle(title, shortTitle); 1197 mFragmentBreadCrumbs.setParentTitle(null, null, null); 1198 } 1199 } 1200 1201 /** 1202 * Should be called after onCreate to ensure that the breadcrumbs, if any, were created. 1203 * This prepends a title to the fragment breadcrumbs and attaches a listener to any clicks 1204 * on the parent entry. 1205 * @param title the title for the breadcrumb 1206 * @param shortTitle the short title for the breadcrumb 1207 */ setParentTitle(CharSequence title, CharSequence shortTitle, OnClickListener listener)1208 public void setParentTitle(CharSequence title, CharSequence shortTitle, 1209 OnClickListener listener) { 1210 if (mFragmentBreadCrumbs != null) { 1211 mFragmentBreadCrumbs.setParentTitle(title, shortTitle, listener); 1212 } 1213 } 1214 setSelectedHeader(Header header)1215 void setSelectedHeader(Header header) { 1216 mCurHeader = header; 1217 int index = mHeaders.indexOf(header); 1218 if (index >= 0) { 1219 getListView().setItemChecked(index, true); 1220 } else { 1221 getListView().clearChoices(); 1222 } 1223 showBreadCrumbs(header); 1224 } 1225 showBreadCrumbs(Header header)1226 void showBreadCrumbs(Header header) { 1227 if (header != null) { 1228 CharSequence title = header.getBreadCrumbTitle(getResources()); 1229 if (title == null) title = header.getTitle(getResources()); 1230 if (title == null) title = getTitle(); 1231 showBreadCrumbs(title, header.getBreadCrumbShortTitle(getResources())); 1232 } else { 1233 showBreadCrumbs(getTitle(), null); 1234 } 1235 } 1236 switchToHeaderInner(String fragmentName, Bundle args)1237 private void switchToHeaderInner(String fragmentName, Bundle args) { 1238 getFragmentManager().popBackStack(BACK_STACK_PREFS, 1239 FragmentManager.POP_BACK_STACK_INCLUSIVE); 1240 if (!isValidFragment(fragmentName)) { 1241 throw new IllegalArgumentException("Invalid fragment for this activity: " 1242 + fragmentName); 1243 } 1244 1245 Fragment f = Fragment.instantiate(this, fragmentName, args); 1246 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 1247 transaction.setTransition(mSinglePane 1248 ? FragmentTransaction.TRANSIT_NONE 1249 : FragmentTransaction.TRANSIT_FRAGMENT_FADE); 1250 transaction.replace(com.android.internal.R.id.prefs, f); 1251 transaction.commitAllowingStateLoss(); 1252 1253 if (mSinglePane && mPrefsContainer.getVisibility() == View.GONE) { 1254 // We are transitioning from headers to preferences panel in single-pane so we need 1255 // to hide headers and show the prefs container. 1256 mPrefsContainer.setVisibility(View.VISIBLE); 1257 mHeadersContainer.setVisibility(View.GONE); 1258 } 1259 } 1260 1261 /** 1262 * When in two-pane mode, switch the fragment pane to show the given 1263 * preference fragment. 1264 * 1265 * @param fragmentName The name of the fragment to display. 1266 * @param args Optional arguments to supply to the fragment. 1267 */ switchToHeader(String fragmentName, Bundle args)1268 public void switchToHeader(String fragmentName, Bundle args) { 1269 Header selectedHeader = null; 1270 for (int i = 0; i < mHeaders.size(); i++) { 1271 if (fragmentName.equals(mHeaders.get(i).fragment)) { 1272 selectedHeader = mHeaders.get(i); 1273 break; 1274 } 1275 } 1276 setSelectedHeader(selectedHeader); 1277 switchToHeaderInner(fragmentName, args); 1278 } 1279 1280 /** 1281 * When in two-pane mode, switch to the fragment pane to show the given 1282 * preference fragment. 1283 * 1284 * @param header The new header to display. 1285 */ switchToHeader(Header header)1286 public void switchToHeader(Header header) { 1287 if (mCurHeader == header) { 1288 // This is the header we are currently displaying. Just make sure 1289 // to pop the stack up to its root state. 1290 getFragmentManager().popBackStack(BACK_STACK_PREFS, 1291 FragmentManager.POP_BACK_STACK_INCLUSIVE); 1292 } else { 1293 if (header.fragment == null) { 1294 throw new IllegalStateException("can't switch to header that has no fragment"); 1295 } 1296 switchToHeaderInner(header.fragment, header.fragmentArguments); 1297 setSelectedHeader(header); 1298 } 1299 } 1300 findBestMatchingHeader(Header cur, ArrayList<Header> from)1301 Header findBestMatchingHeader(Header cur, ArrayList<Header> from) { 1302 ArrayList<Header> matches = new ArrayList<Header>(); 1303 for (int j=0; j<from.size(); j++) { 1304 Header oh = from.get(j); 1305 if (cur == oh || (cur.id != HEADER_ID_UNDEFINED && cur.id == oh.id)) { 1306 // Must be this one. 1307 matches.clear(); 1308 matches.add(oh); 1309 break; 1310 } 1311 if (cur.fragment != null) { 1312 if (cur.fragment.equals(oh.fragment)) { 1313 matches.add(oh); 1314 } 1315 } else if (cur.intent != null) { 1316 if (cur.intent.equals(oh.intent)) { 1317 matches.add(oh); 1318 } 1319 } else if (cur.title != null) { 1320 if (cur.title.equals(oh.title)) { 1321 matches.add(oh); 1322 } 1323 } 1324 } 1325 final int NM = matches.size(); 1326 if (NM == 1) { 1327 return matches.get(0); 1328 } else if (NM > 1) { 1329 for (int j=0; j<NM; j++) { 1330 Header oh = matches.get(j); 1331 if (cur.fragmentArguments != null && 1332 cur.fragmentArguments.equals(oh.fragmentArguments)) { 1333 return oh; 1334 } 1335 if (cur.extras != null && cur.extras.equals(oh.extras)) { 1336 return oh; 1337 } 1338 if (cur.title != null && cur.title.equals(oh.title)) { 1339 return oh; 1340 } 1341 } 1342 } 1343 return null; 1344 } 1345 1346 /** 1347 * Start a new fragment. 1348 * 1349 * @param fragment The fragment to start 1350 * @param push If true, the current fragment will be pushed onto the back stack. If false, 1351 * the current fragment will be replaced. 1352 */ startPreferenceFragment(Fragment fragment, boolean push)1353 public void startPreferenceFragment(Fragment fragment, boolean push) { 1354 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 1355 transaction.replace(com.android.internal.R.id.prefs, fragment); 1356 if (push) { 1357 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); 1358 transaction.addToBackStack(BACK_STACK_PREFS); 1359 } else { 1360 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 1361 } 1362 transaction.commitAllowingStateLoss(); 1363 } 1364 1365 /** 1366 * Start a new fragment containing a preference panel. If the preferences 1367 * are being displayed in multi-pane mode, the given fragment class will 1368 * be instantiated and placed in the appropriate pane. If running in 1369 * single-pane mode, a new activity will be launched in which to show the 1370 * fragment. 1371 * 1372 * @param fragmentClass Full name of the class implementing the fragment. 1373 * @param args Any desired arguments to supply to the fragment. 1374 * @param titleRes Optional resource identifier of the title of this 1375 * fragment. 1376 * @param titleText Optional text of the title of this fragment. 1377 * @param resultTo Optional fragment that result data should be sent to. 1378 * If non-null, resultTo.onActivityResult() will be called when this 1379 * preference panel is done. The launched panel must use 1380 * {@link #finishPreferencePanel(Fragment, int, Intent)} when done. 1381 * @param resultRequestCode If resultTo is non-null, this is the caller's 1382 * request code to be received with the result. 1383 */ startPreferencePanel(String fragmentClass, Bundle args, @StringRes int titleRes, CharSequence titleText, Fragment resultTo, int resultRequestCode)1384 public void startPreferencePanel(String fragmentClass, Bundle args, @StringRes int titleRes, 1385 CharSequence titleText, Fragment resultTo, int resultRequestCode) { 1386 Fragment f = Fragment.instantiate(this, fragmentClass, args); 1387 if (resultTo != null) { 1388 f.setTargetFragment(resultTo, resultRequestCode); 1389 } 1390 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 1391 transaction.replace(com.android.internal.R.id.prefs, f); 1392 if (titleRes != 0) { 1393 transaction.setBreadCrumbTitle(titleRes); 1394 } else if (titleText != null) { 1395 transaction.setBreadCrumbTitle(titleText); 1396 } 1397 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); 1398 transaction.addToBackStack(BACK_STACK_PREFS); 1399 transaction.commitAllowingStateLoss(); 1400 } 1401 1402 /** 1403 * Called by a preference panel fragment to finish itself. 1404 * 1405 * @param caller The fragment that is asking to be finished. 1406 * @param resultCode Optional result code to send back to the original 1407 * launching fragment. 1408 * @param resultData Optional result data to send back to the original 1409 * launching fragment. 1410 */ finishPreferencePanel(Fragment caller, int resultCode, Intent resultData)1411 public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) { 1412 // TODO: be smarter about popping the stack. 1413 onBackPressed(); 1414 if (caller != null) { 1415 if (caller.getTargetFragment() != null) { 1416 caller.getTargetFragment().onActivityResult(caller.getTargetRequestCode(), 1417 resultCode, resultData); 1418 } 1419 } 1420 } 1421 1422 @Override onPreferenceStartFragment(PreferenceFragment caller, Preference pref)1423 public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) { 1424 startPreferencePanel(pref.getFragment(), pref.getExtras(), pref.getTitleRes(), 1425 pref.getTitle(), null, 0); 1426 return true; 1427 } 1428 1429 /** 1430 * Posts a message to bind the preferences to the list view. 1431 * <p> 1432 * Binding late is preferred as any custom preference types created in 1433 * {@link #onCreate(Bundle)} are able to have their views recycled. 1434 */ 1435 @UnsupportedAppUsage postBindPreferences()1436 private void postBindPreferences() { 1437 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return; 1438 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget(); 1439 } 1440 bindPreferences()1441 private void bindPreferences() { 1442 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 1443 if (preferenceScreen != null) { 1444 preferenceScreen.bind(getListView()); 1445 if (mSavedInstanceState != null) { 1446 super.onRestoreInstanceState(mSavedInstanceState); 1447 mSavedInstanceState = null; 1448 } 1449 } 1450 } 1451 1452 /** 1453 * Returns the {@link PreferenceManager} used by this activity. 1454 * @return The {@link PreferenceManager}. 1455 * 1456 * @deprecated This function is not relevant for a modern fragment-based 1457 * PreferenceActivity. 1458 */ 1459 @Deprecated getPreferenceManager()1460 public PreferenceManager getPreferenceManager() { 1461 return mPreferenceManager; 1462 } 1463 1464 @UnsupportedAppUsage requirePreferenceManager()1465 private void requirePreferenceManager() { 1466 if (mPreferenceManager == null) { 1467 if (mAdapter == null) { 1468 throw new RuntimeException("This should be called after super.onCreate."); 1469 } 1470 throw new RuntimeException( 1471 "Modern two-pane PreferenceActivity requires use of a PreferenceFragment"); 1472 } 1473 } 1474 1475 /** 1476 * Sets the root of the preference hierarchy that this activity is showing. 1477 * 1478 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. 1479 * 1480 * @deprecated This function is not relevant for a modern fragment-based 1481 * PreferenceActivity. 1482 */ 1483 @Deprecated setPreferenceScreen(PreferenceScreen preferenceScreen)1484 public void setPreferenceScreen(PreferenceScreen preferenceScreen) { 1485 requirePreferenceManager(); 1486 1487 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) { 1488 postBindPreferences(); 1489 CharSequence title = getPreferenceScreen().getTitle(); 1490 // Set the title of the activity 1491 if (title != null) { 1492 setTitle(title); 1493 } 1494 } 1495 } 1496 1497 /** 1498 * Gets the root of the preference hierarchy that this activity is showing. 1499 * 1500 * @return The {@link PreferenceScreen} that is the root of the preference 1501 * hierarchy. 1502 * 1503 * @deprecated This function is not relevant for a modern fragment-based 1504 * PreferenceActivity. 1505 */ 1506 @Deprecated getPreferenceScreen()1507 public PreferenceScreen getPreferenceScreen() { 1508 if (mPreferenceManager != null) { 1509 return mPreferenceManager.getPreferenceScreen(); 1510 } 1511 return null; 1512 } 1513 1514 /** 1515 * Adds preferences from activities that match the given {@link Intent}. 1516 * 1517 * @param intent The {@link Intent} to query activities. 1518 * 1519 * @deprecated This function is not relevant for a modern fragment-based 1520 * PreferenceActivity. 1521 */ 1522 @Deprecated addPreferencesFromIntent(Intent intent)1523 public void addPreferencesFromIntent(Intent intent) { 1524 requirePreferenceManager(); 1525 1526 setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen())); 1527 } 1528 1529 /** 1530 * Inflates the given XML resource and adds the preference hierarchy to the current 1531 * preference hierarchy. 1532 * 1533 * @param preferencesResId The XML resource ID to inflate. 1534 * 1535 * @deprecated This function is not relevant for a modern fragment-based 1536 * PreferenceActivity. 1537 */ 1538 @Deprecated addPreferencesFromResource(int preferencesResId)1539 public void addPreferencesFromResource(int preferencesResId) { 1540 requirePreferenceManager(); 1541 1542 setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId, 1543 getPreferenceScreen())); 1544 } 1545 1546 /** 1547 * {@inheritDoc} 1548 * 1549 * @deprecated This function is not relevant for a modern fragment-based 1550 * PreferenceActivity. 1551 */ 1552 @Deprecated onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)1553 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { 1554 return false; 1555 } 1556 1557 /** 1558 * Finds a {@link Preference} based on its key. 1559 * 1560 * @param key The key of the preference to retrieve. 1561 * @return The {@link Preference} with the key, or null. 1562 * @see PreferenceGroup#findPreference(CharSequence) 1563 * 1564 * @deprecated This function is not relevant for a modern fragment-based 1565 * PreferenceActivity. 1566 */ 1567 @Deprecated findPreference(CharSequence key)1568 public Preference findPreference(CharSequence key) { 1569 1570 if (mPreferenceManager == null) { 1571 return null; 1572 } 1573 1574 return mPreferenceManager.findPreference(key); 1575 } 1576 1577 @Override onNewIntent(Intent intent)1578 protected void onNewIntent(Intent intent) { 1579 if (mPreferenceManager != null) { 1580 mPreferenceManager.dispatchNewIntent(intent); 1581 } 1582 } 1583 1584 // give subclasses access to the Next button 1585 /** @hide */ hasNextButton()1586 protected boolean hasNextButton() { 1587 return mNextButton != null; 1588 } 1589 /** @hide */ getNextButton()1590 protected Button getNextButton() { 1591 return mNextButton; 1592 } 1593 } 1594