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