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