1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.preference; 18 19 import android.animation.LayoutTransition; 20 import android.annotation.Nullable; 21 import android.annotation.StringRes; 22 import android.annotation.XmlRes; 23 import android.app.Fragment; 24 import android.app.FragmentBreadCrumbs; 25 import android.app.FragmentManager; 26 import android.app.FragmentTransaction; 27 import android.app.ListActivity; 28 import android.compat.annotation.UnsupportedAppUsage; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.res.Resources; 32 import android.content.res.TypedArray; 33 import android.content.res.XmlResourceParser; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.Message; 37 import android.os.Parcel; 38 import android.os.Parcelable; 39 import android.text.TextUtils; 40 import android.util.AttributeSet; 41 import android.util.TypedValue; 42 import android.util.Xml; 43 import android.view.LayoutInflater; 44 import android.view.MenuItem; 45 import android.view.View; 46 import android.view.View.OnClickListener; 47 import android.view.ViewGroup; 48 import android.widget.AbsListView; 49 import android.widget.ArrayAdapter; 50 import android.widget.BaseAdapter; 51 import android.widget.Button; 52 import android.widget.FrameLayout; 53 import android.widget.ImageView; 54 import android.widget.ListView; 55 import android.widget.TextView; 56 57 import com.android.internal.util.XmlUtils; 58 59 import org.xmlpull.v1.XmlPullParser; 60 import org.xmlpull.v1.XmlPullParserException; 61 62 import java.io.IOException; 63 import java.util.ArrayList; 64 import java.util.List; 65 66 /** 67 * This is the base class for an activity to show a hierarchy of preferences 68 * to the user. Prior to {@link android.os.Build.VERSION_CODES#HONEYCOMB} 69 * this class only allowed the display of a single set of preference; this 70 * functionality should now be found in the new {@link PreferenceFragment} 71 * class. If you are using PreferenceActivity in its old mode, the documentation 72 * there applies to the deprecated APIs here. 73 * 74 * <p>This activity shows one or more headers of preferences, each of which 75 * is associated with a {@link PreferenceFragment} to display the preferences 76 * of that header. The actual layout and display of these associations can 77 * however vary; currently there are two major approaches it may take: 78 * 79 * <ul> 80 * <li>On a small screen it may display only the headers as a single list when first launched. 81 * Selecting one of the header items will only show the PreferenceFragment of that header (on 82 * Android N and lower a new Activity is launched). 83 * <li>On a large screen it may display both the headers and current PreferenceFragment together as 84 * panes. Selecting a header item switches to showing the correct PreferenceFragment for that item. 85 * </ul> 86 * 87 * <p>Subclasses of PreferenceActivity should implement 88 * {@link #onBuildHeaders} to populate the header list with the desired 89 * items. Doing this implicitly switches the class into its new "headers 90 * + fragments" mode rather than the old style of just showing a single 91 * preferences list. 92 * 93 * <div class="special reference"> 94 * <h3>Developer Guides</h3> 95 * <p>For information about using {@code PreferenceActivity}, 96 * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a> 97 * guide.</p> 98 * </div> 99 * 100 * @deprecated Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 101 * <a href="{@docRoot}reference/androidx/preference/package-summary.html"> 102 * Preference Library</a> for consistent behavior across all devices. For more information on 103 * using the AndroidX Preference Library see 104 * <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>. 105 */ 106 @Deprecated 107 public abstract class PreferenceActivity extends ListActivity implements 108 PreferenceManager.OnPreferenceTreeClickListener, 109 PreferenceFragment.OnPreferenceStartFragmentCallback { 110 111 private static final String TAG = "PreferenceActivity"; 112 113 // Constants for state save/restore 114 private static final String HEADERS_TAG = ":android:headers"; 115 private static final String CUR_HEADER_TAG = ":android:cur_header"; 116 private static final String PREFERENCES_TAG = ":android:preferences"; 117 118 /** 119 * When starting this activity, the invoking Intent can contain this extra 120 * string to specify which fragment should be initially displayed. 121 * <p/>Starting from Key Lime Pie, when this argument is passed in, the PreferenceActivity 122 * will call isValidFragment() to confirm that the fragment class name is valid for this 123 * activity. 124 */ 125 public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment"; 126 127 /** 128 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 129 * this extra can also be specified to supply a Bundle of arguments to pass 130 * to that fragment when it is instantiated during the initial creation 131 * of PreferenceActivity. 132 */ 133 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args"; 134 135 /** 136 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 137 * this extra can also be specify to supply the title to be shown for 138 * that fragment. 139 */ 140 public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":android:show_fragment_title"; 141 142 /** 143 * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, 144 * this extra can also be specify to supply the short title to be shown for 145 * that fragment. 146 */ 147 public static final String EXTRA_SHOW_FRAGMENT_SHORT_TITLE 148 = ":android:show_fragment_short_title"; 149 150 /** 151 * When starting this activity, the invoking Intent can contain this extra 152 * boolean that the header list should not be displayed. This is most often 153 * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch 154 * the activity to display a specific fragment that the user has navigated 155 * to. 156 */ 157 public static final String EXTRA_NO_HEADERS = ":android:no_headers"; 158 159 private static final String BACK_STACK_PREFS = ":android:prefs"; 160 161 // extras that allow any preference activity to be launched as part of a wizard 162 163 // show Back and Next buttons? takes boolean parameter 164 // Back will then return RESULT_CANCELED and Next RESULT_OK 165 private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar"; 166 167 // add a Skip button? 168 private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip"; 169 170 // specify custom text for the Back or Next buttons, or cause a button to not appear 171 // at all by setting it to null 172 private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text"; 173 private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text"; 174 175 // --- State for new mode when showing a list of headers + prefs fragment 176 177 private final ArrayList<Header> mHeaders = new ArrayList<Header>(); 178 179 private FrameLayout mListFooter; 180 181 @UnsupportedAppUsage 182 private ViewGroup mPrefsContainer; 183 184 // Backup of the original activity title. This is used when navigating back to the headers list 185 // in onBackPress to restore the title. 186 private CharSequence mActivityTitle; 187 188 // Null if in legacy mode. 189 private ViewGroup mHeadersContainer; 190 191 private FragmentBreadCrumbs mFragmentBreadCrumbs; 192 193 private boolean mSinglePane; 194 195 private Header mCurHeader; 196 197 // --- State for old mode when showing a single preference list 198 199 @UnsupportedAppUsage 200 private PreferenceManager mPreferenceManager; 201 202 private Bundle mSavedInstanceState; 203 204 // --- Common state 205 206 private Button mNextButton; 207 208 private int mPreferenceHeaderItemResId = 0; 209 private boolean mPreferenceHeaderRemoveEmptyIcon = false; 210 211 /** 212 * The starting request code given out to preference framework. 213 */ 214 private static final int FIRST_REQUEST_CODE = 100; 215 216 private static final int MSG_BIND_PREFERENCES = 1; 217 private static final int MSG_BUILD_HEADERS = 2; 218 private Handler mHandler = new Handler() { 219 @Override 220 public void handleMessage(Message msg) { 221 switch (msg.what) { 222 case MSG_BIND_PREFERENCES: { 223 bindPreferences(); 224 } break; 225 case MSG_BUILD_HEADERS: { 226 ArrayList<Header> oldHeaders = new ArrayList<Header>(mHeaders); 227 mHeaders.clear(); 228 onBuildHeaders(mHeaders); 229 if (mAdapter instanceof BaseAdapter) { 230 ((BaseAdapter) mAdapter).notifyDataSetChanged(); 231 } 232 Header header = onGetNewHeader(); 233 if (header != null && header.fragment != null) { 234 Header mappedHeader = findBestMatchingHeader(header, oldHeaders); 235 if (mappedHeader == null || mCurHeader != mappedHeader) { 236 switchToHeader(header); 237 } 238 } else if (mCurHeader != null) { 239 Header mappedHeader = findBestMatchingHeader(mCurHeader, mHeaders); 240 if (mappedHeader != null) { 241 setSelectedHeader(mappedHeader); 242 } 243 } 244 } break; 245 } 246 } 247 }; 248 249 private static class HeaderAdapter extends ArrayAdapter<Header> { 250 private static class HeaderViewHolder { 251 ImageView icon; 252 TextView title; 253 TextView summary; 254 } 255 256 private LayoutInflater mInflater; 257 private int mLayoutResId; 258 private boolean mRemoveIconIfEmpty; 259 HeaderAdapter(Context context, List<Header> objects, int layoutResId, boolean removeIconBehavior)260 public HeaderAdapter(Context context, List<Header> objects, int layoutResId, 261 boolean removeIconBehavior) { 262 super(context, 0, objects); 263 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 264 mLayoutResId = layoutResId; 265 mRemoveIconIfEmpty = removeIconBehavior; 266 } 267 268 @Override getView(int position, View convertView, ViewGroup parent)269 public View getView(int position, View convertView, ViewGroup parent) { 270 HeaderViewHolder holder; 271 View view; 272 273 if (convertView == null) { 274 view = mInflater.inflate(mLayoutResId, parent, false); 275 holder = new HeaderViewHolder(); 276 holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon); 277 holder.title = (TextView) view.findViewById(com.android.internal.R.id.title); 278 holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary); 279 view.setTag(holder); 280 } else { 281 view = convertView; 282 holder = (HeaderViewHolder) view.getTag(); 283 } 284 285 // All view fields must be updated every time, because the view may be recycled 286 Header header = getItem(position); 287 if (mRemoveIconIfEmpty) { 288 if (header.iconRes == 0) { 289 holder.icon.setVisibility(View.GONE); 290 } else { 291 holder.icon.setVisibility(View.VISIBLE); 292 holder.icon.setImageResource(header.iconRes); 293 } 294 } else { 295 holder.icon.setImageResource(header.iconRes); 296 } 297 holder.title.setText(header.getTitle(getContext().getResources())); 298 CharSequence summary = header.getSummary(getContext().getResources()); 299 if (!TextUtils.isEmpty(summary)) { 300 holder.summary.setVisibility(View.VISIBLE); 301 holder.summary.setText(summary); 302 } else { 303 holder.summary.setVisibility(View.GONE); 304 } 305 306 return view; 307 } 308 } 309 310 /** 311 * Default value for {@link Header#id Header.id} indicating that no 312 * identifier value is set. All other values (including those below -1) 313 * are valid. 314 */ 315 public static final long HEADER_ID_UNDEFINED = -1; 316 317 /** 318 * Description of a single Header item that the user can select. 319 * 320 * @deprecated Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 321 * <a href="{@docRoot}reference/androidx/preference/package-summary.html"> 322 * Preference Library</a> for consistent behavior across all devices. 323 * For more information on using the AndroidX Preference Library see 324 * <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>. 325 */ 326 @Deprecated 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 @StringRes 341 public int titleRes; 342 343 /** 344 * Title of the header that is shown to the user. 345 * @attr ref android.R.styleable#PreferenceHeader_title 346 */ 347 public CharSequence title; 348 349 /** 350 * Resource ID of optional summary describing what this header controls. 351 * @attr ref android.R.styleable#PreferenceHeader_summary 352 */ 353 @StringRes 354 public int summaryRes; 355 356 /** 357 * Optional summary describing what this header controls. 358 * @attr ref android.R.styleable#PreferenceHeader_summary 359 */ 360 public CharSequence summary; 361 362 /** 363 * Resource ID of optional text to show as the title in the bread crumb. 364 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle 365 */ 366 @StringRes 367 public int breadCrumbTitleRes; 368 369 /** 370 * Optional text to show as the title in the bread crumb. 371 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle 372 */ 373 public CharSequence breadCrumbTitle; 374 375 /** 376 * Resource ID of optional text to show as the short title in the bread crumb. 377 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle 378 */ 379 @StringRes 380 public int breadCrumbShortTitleRes; 381 382 /** 383 * Optional text to show as the short title in the bread crumb. 384 * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle 385 */ 386 public CharSequence breadCrumbShortTitle; 387 388 /** 389 * Optional icon resource to show for this header. 390 * @attr ref android.R.styleable#PreferenceHeader_icon 391 */ 392 public int iconRes; 393 394 /** 395 * Full class name of the fragment to display when this header is 396 * selected. 397 * @attr ref android.R.styleable#PreferenceHeader_fragment 398 */ 399 public String fragment; 400 401 /** 402 * Optional arguments to supply to the fragment when it is 403 * instantiated. 404 */ 405 public Bundle fragmentArguments; 406 407 /** 408 * Intent to launch when the preference is selected. 409 */ 410 public Intent intent; 411 412 /** 413 * Optional additional data for use by subclasses of PreferenceActivity. 414 */ 415 public Bundle extras; 416 Header()417 public Header() { 418 // Empty 419 } 420 421 /** 422 * Return the currently set title. If {@link #titleRes} is set, 423 * this resource is loaded from <var>res</var> and returned. Otherwise 424 * {@link #title} is returned. 425 */ getTitle(Resources res)426 public CharSequence getTitle(Resources res) { 427 if (titleRes != 0) { 428 return res.getText(titleRes); 429 } 430 return title; 431 } 432 433 /** 434 * Return the currently set summary. If {@link #summaryRes} is set, 435 * this resource is loaded from <var>res</var> and returned. Otherwise 436 * {@link #summary} is returned. 437 */ getSummary(Resources res)438 public CharSequence getSummary(Resources res) { 439 if (summaryRes != 0) { 440 return res.getText(summaryRes); 441 } 442 return summary; 443 } 444 445 /** 446 * Return the currently set bread crumb title. If {@link #breadCrumbTitleRes} is set, 447 * this resource is loaded from <var>res</var> and returned. Otherwise 448 * {@link #breadCrumbTitle} is returned. 449 */ getBreadCrumbTitle(Resources res)450 public CharSequence getBreadCrumbTitle(Resources res) { 451 if (breadCrumbTitleRes != 0) { 452 return res.getText(breadCrumbTitleRes); 453 } 454 return breadCrumbTitle; 455 } 456 457 /** 458 * Return the currently set bread crumb short title. If 459 * {@link #breadCrumbShortTitleRes} is set, 460 * this resource is loaded from <var>res</var> and returned. Otherwise 461 * {@link #breadCrumbShortTitle} is returned. 462 */ getBreadCrumbShortTitle(Resources res)463 public CharSequence getBreadCrumbShortTitle(Resources res) { 464 if (breadCrumbShortTitleRes != 0) { 465 return res.getText(breadCrumbShortTitleRes); 466 } 467 return breadCrumbShortTitle; 468 } 469 470 @Override describeContents()471 public int describeContents() { 472 return 0; 473 } 474 475 @Override writeToParcel(Parcel dest, int flags)476 public void writeToParcel(Parcel dest, int flags) { 477 dest.writeLong(id); 478 dest.writeInt(titleRes); 479 TextUtils.writeToParcel(title, dest, flags); 480 dest.writeInt(summaryRes); 481 TextUtils.writeToParcel(summary, dest, flags); 482 dest.writeInt(breadCrumbTitleRes); 483 TextUtils.writeToParcel(breadCrumbTitle, dest, flags); 484 dest.writeInt(breadCrumbShortTitleRes); 485 TextUtils.writeToParcel(breadCrumbShortTitle, dest, flags); 486 dest.writeInt(iconRes); 487 dest.writeString(fragment); 488 dest.writeBundle(fragmentArguments); 489 if (intent != null) { 490 dest.writeInt(1); 491 intent.writeToParcel(dest, flags); 492 } else { 493 dest.writeInt(0); 494 } 495 dest.writeBundle(extras); 496 } 497 readFromParcel(Parcel in)498 public void readFromParcel(Parcel in) { 499 id = in.readLong(); 500 titleRes = in.readInt(); 501 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 502 summaryRes = in.readInt(); 503 summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 504 breadCrumbTitleRes = in.readInt(); 505 breadCrumbTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 506 breadCrumbShortTitleRes = in.readInt(); 507 breadCrumbShortTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 508 iconRes = in.readInt(); 509 fragment = in.readString(); 510 fragmentArguments = in.readBundle(); 511 if (in.readInt() != 0) { 512 intent = Intent.CREATOR.createFromParcel(in); 513 } 514 extras = in.readBundle(); 515 } 516 Header(Parcel in)517 Header(Parcel in) { 518 readFromParcel(in); 519 } 520 521 public static final @android.annotation.NonNull Creator<Header> CREATOR = new Creator<Header>() { 522 public Header createFromParcel(Parcel source) { 523 return new Header(source); 524 } 525 public Header[] newArray(int size) { 526 return new Header[size]; 527 } 528 }; 529 } 530 531 @Override onOptionsItemSelected(MenuItem item)532 public boolean onOptionsItemSelected(MenuItem item) { 533 if (item.getItemId() == android.R.id.home) { 534 // Override home navigation button to call onBackPressed (b/35152749). 535 onBackPressed(); 536 return true; 537 } 538 return super.onOptionsItemSelected(item); 539 } 540 541 @Override onCreate(@ullable Bundle savedInstanceState)542 protected void onCreate(@Nullable Bundle savedInstanceState) { 543 super.onCreate(savedInstanceState); 544 545 // Theming for the PreferenceActivity layout and for the Preference Header(s) layout 546 TypedArray sa = obtainStyledAttributes(null, 547 com.android.internal.R.styleable.PreferenceActivity, 548 com.android.internal.R.attr.preferenceActivityStyle, 549 0); 550 551 final int layoutResId = sa.getResourceId( 552 com.android.internal.R.styleable.PreferenceActivity_layout, 553 com.android.internal.R.layout.preference_list_content); 554 555 mPreferenceHeaderItemResId = sa.getResourceId( 556 com.android.internal.R.styleable.PreferenceActivity_headerLayout, 557 com.android.internal.R.layout.preference_header_item); 558 mPreferenceHeaderRemoveEmptyIcon = sa.getBoolean( 559 com.android.internal.R.styleable.PreferenceActivity_headerRemoveIconIfEmpty, 560 false); 561 562 sa.recycle(); 563 564 setContentView(layoutResId); 565 566 mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer); 567 mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs_frame); 568 mHeadersContainer = (ViewGroup) findViewById(com.android.internal.R.id.headers); 569 boolean hidingHeaders = onIsHidingHeaders(); 570 mSinglePane = hidingHeaders || !onIsMultiPane(); 571 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT); 572 Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); 573 int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0); 574 int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0); 575 mActivityTitle = getTitle(); 576 577 if (savedInstanceState != null) { 578 // We are restarting from a previous saved state; used that to 579 // initialize, instead of starting fresh. 580 ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG); 581 if (headers != null) { 582 mHeaders.addAll(headers); 583 int curHeader = savedInstanceState.getInt(CUR_HEADER_TAG, 584 (int) HEADER_ID_UNDEFINED); 585 if (curHeader >= 0 && curHeader < mHeaders.size()) { 586 setSelectedHeader(mHeaders.get(curHeader)); 587 } else if (!mSinglePane && initialFragment == null) { 588 switchToHeader(onGetInitialHeader()); 589 } 590 } else { 591 // This will for instance hide breadcrumbs for single pane. 592 showBreadCrumbs(getTitle(), null); 593 } 594 } else { 595 if (!onIsHidingHeaders()) { 596 onBuildHeaders(mHeaders); 597 } 598 599 if (initialFragment != null) { 600 switchToHeader(initialFragment, initialArguments); 601 } else if (!mSinglePane && mHeaders.size() > 0) { 602 switchToHeader(onGetInitialHeader()); 603 } 604 } 605 606 if (mHeaders.size() > 0) { 607 setListAdapter(new HeaderAdapter(this, mHeaders, mPreferenceHeaderItemResId, 608 mPreferenceHeaderRemoveEmptyIcon)); 609 if (!mSinglePane) { 610 getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); 611 } 612 } 613 614 if (mSinglePane && initialFragment != null && initialTitle != 0) { 615 CharSequence initialTitleStr = getText(initialTitle); 616 CharSequence initialShortTitleStr = initialShortTitle != 0 617 ? getText(initialShortTitle) : null; 618 showBreadCrumbs(initialTitleStr, initialShortTitleStr); 619 } 620 621 if (mHeaders.size() == 0 && initialFragment == null) { 622 // If there are no headers, we are in the old "just show a screen 623 // of preferences" mode. 624 setContentView(com.android.internal.R.layout.preference_list_content_single); 625 mListFooter = (FrameLayout) findViewById(com.android.internal.R.id.list_footer); 626 mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs); 627 mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE); 628 mPreferenceManager.setOnPreferenceTreeClickListener(this); 629 mHeadersContainer = null; 630 } else if (mSinglePane) { 631 // Single-pane so one of the header or prefs containers must be hidden. 632 if (initialFragment != null || mCurHeader != null) { 633 mHeadersContainer.setVisibility(View.GONE); 634 } else { 635 mPrefsContainer.setVisibility(View.GONE); 636 } 637 638 // This animates our manual transitions between headers and prefs panel in single-pane. 639 // It also comes last so we don't animate any initial layout changes done above. 640 ViewGroup container = (ViewGroup) findViewById( 641 com.android.internal.R.id.prefs_container); 642 container.setLayoutTransition(new LayoutTransition()); 643 } else { 644 // Multi-pane 645 if (mHeaders.size() > 0 && mCurHeader != null) { 646 setSelectedHeader(mCurHeader); 647 } 648 } 649 650 // see if we should show Back/Next buttons 651 Intent intent = getIntent(); 652 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) { 653 654 findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE); 655 656 Button backButton = (Button)findViewById(com.android.internal.R.id.back_button); 657 backButton.setOnClickListener(new OnClickListener() { 658 public void onClick(View v) { 659 setResult(RESULT_CANCELED); 660 finish(); 661 } 662 }); 663 Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button); 664 skipButton.setOnClickListener(new OnClickListener() { 665 public void onClick(View v) { 666 setResult(RESULT_OK); 667 finish(); 668 } 669 }); 670 mNextButton = (Button)findViewById(com.android.internal.R.id.next_button); 671 mNextButton.setOnClickListener(new OnClickListener() { 672 public void onClick(View v) { 673 setResult(RESULT_OK); 674 finish(); 675 } 676 }); 677 678 // set our various button parameters 679 if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) { 680 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT); 681 if (TextUtils.isEmpty(buttonText)) { 682 mNextButton.setVisibility(View.GONE); 683 } 684 else { 685 mNextButton.setText(buttonText); 686 } 687 } 688 if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) { 689 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT); 690 if (TextUtils.isEmpty(buttonText)) { 691 backButton.setVisibility(View.GONE); 692 } 693 else { 694 backButton.setText(buttonText); 695 } 696 } 697 if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) { 698 skipButton.setVisibility(View.VISIBLE); 699 } 700 } 701 } 702 703 @Override onBackPressed()704 public void onBackPressed() { 705 if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0 706 && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) { 707 mCurHeader = null; 708 709 mPrefsContainer.setVisibility(View.GONE); 710 mHeadersContainer.setVisibility(View.VISIBLE); 711 if (mActivityTitle != null) { 712 showBreadCrumbs(mActivityTitle, null); 713 } 714 getListView().clearChoices(); 715 } else { 716 super.onBackPressed(); 717 } 718 } 719 720 /** 721 * Returns true if this activity is currently showing the header list. 722 */ hasHeaders()723 public boolean hasHeaders() { 724 return mHeadersContainer != null && mHeadersContainer.getVisibility() == View.VISIBLE; 725 } 726 727 /** 728 * Returns the Header list 729 * @hide 730 */ 731 @UnsupportedAppUsage getHeaders()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 */ isMultiPane()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 */ onIsMultiPane()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 */ onIsHidingHeaders()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 */ onGetInitialHeader()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 */ onGetNewHeader()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 */ onBuildHeaders(List<Header> target)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 */ invalidateHeaders()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 */ loadHeadersFromResource(@mlRes int resid, List<Header> target)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 */ isValidFragment(String fragmentName)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 */ setListFooter(View view)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 onStop()981 protected void onStop() { 982 super.onStop(); 983 984 if (mPreferenceManager != null) { 985 mPreferenceManager.dispatchActivityStop(); 986 } 987 } 988 989 @Override onDestroy()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 onSaveInstanceState(Bundle outState)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 onRestoreInstanceState(Bundle state)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 onActivityResult(int requestCode, int resultCode, Intent data)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 onContentChanged()1060 public void onContentChanged() { 1061 super.onContentChanged(); 1062 1063 if (mPreferenceManager != null) { 1064 postBindPreferences(); 1065 } 1066 } 1067 1068 @Override onListItemClick(ListView l, View v, int position, long id)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 */ onHeaderClick(Header header, int position)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 */ onBuildStartFragmentIntent(String fragmentName, Bundle args, @StringRes int titleRes, int shortTitleRes)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 */ startWithFragment(String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode)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 */ startWithFragment(String fragmentName, Bundle args, Fragment resultTo, int resultRequestCode, @StringRes int titleRes, @StringRes int shortTitleRes)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 */ showBreadCrumbs(CharSequence title, CharSequence shortTitle)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 */ setParentTitle(CharSequence title, CharSequence shortTitle, OnClickListener listener)1207 public void setParentTitle(CharSequence title, CharSequence shortTitle, 1208 OnClickListener listener) { 1209 if (mFragmentBreadCrumbs != null) { 1210 mFragmentBreadCrumbs.setParentTitle(title, shortTitle, listener); 1211 } 1212 } 1213 setSelectedHeader(Header header)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 showBreadCrumbs(Header header)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 switchToHeaderInner(String fragmentName, Bundle args)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 */ switchToHeader(String fragmentName, Bundle args)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 */ switchToHeader(Header header)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 findBestMatchingHeader(Header cur, ArrayList<Header> from)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 */ startPreferenceFragment(Fragment fragment, boolean push)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 */ startPreferencePanel(String fragmentClass, Bundle args, @StringRes int titleRes, CharSequence titleText, Fragment resultTo, int resultRequestCode)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 */ finishPreferencePanel(Fragment caller, int resultCode, Intent resultData)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 onPreferenceStartFragment(PreferenceFragment caller, Preference pref)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 @UnsupportedAppUsage postBindPreferences()1435 private void postBindPreferences() { 1436 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return; 1437 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget(); 1438 } 1439 bindPreferences()1440 private void bindPreferences() { 1441 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 1442 if (preferenceScreen != null) { 1443 preferenceScreen.bind(getListView()); 1444 if (mSavedInstanceState != null) { 1445 super.onRestoreInstanceState(mSavedInstanceState); 1446 mSavedInstanceState = null; 1447 } 1448 } 1449 } 1450 1451 /** 1452 * Returns the {@link PreferenceManager} used by this activity. 1453 * @return The {@link PreferenceManager}. 1454 * 1455 * @deprecated This function is not relevant for a modern fragment-based 1456 * PreferenceActivity. 1457 */ 1458 @Deprecated getPreferenceManager()1459 public PreferenceManager getPreferenceManager() { 1460 return mPreferenceManager; 1461 } 1462 1463 @UnsupportedAppUsage requirePreferenceManager()1464 private void requirePreferenceManager() { 1465 if (mPreferenceManager == null) { 1466 if (mAdapter == null) { 1467 throw new RuntimeException("This should be called after super.onCreate."); 1468 } 1469 throw new RuntimeException( 1470 "Modern two-pane PreferenceActivity requires use of a PreferenceFragment"); 1471 } 1472 } 1473 1474 /** 1475 * Sets the root of the preference hierarchy that this activity is showing. 1476 * 1477 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. 1478 * 1479 * @deprecated This function is not relevant for a modern fragment-based 1480 * PreferenceActivity. 1481 */ 1482 @Deprecated setPreferenceScreen(PreferenceScreen preferenceScreen)1483 public void setPreferenceScreen(PreferenceScreen preferenceScreen) { 1484 requirePreferenceManager(); 1485 1486 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) { 1487 postBindPreferences(); 1488 CharSequence title = getPreferenceScreen().getTitle(); 1489 // Set the title of the activity 1490 if (title != null) { 1491 setTitle(title); 1492 } 1493 } 1494 } 1495 1496 /** 1497 * Gets the root of the preference hierarchy that this activity is showing. 1498 * 1499 * @return The {@link PreferenceScreen} that is the root of the preference 1500 * hierarchy. 1501 * 1502 * @deprecated This function is not relevant for a modern fragment-based 1503 * PreferenceActivity. 1504 */ 1505 @Deprecated getPreferenceScreen()1506 public PreferenceScreen getPreferenceScreen() { 1507 if (mPreferenceManager != null) { 1508 return mPreferenceManager.getPreferenceScreen(); 1509 } 1510 return null; 1511 } 1512 1513 /** 1514 * Adds preferences from activities that match the given {@link Intent}. 1515 * 1516 * @param intent The {@link Intent} to query activities. 1517 * 1518 * @deprecated This function is not relevant for a modern fragment-based 1519 * PreferenceActivity. 1520 */ 1521 @Deprecated addPreferencesFromIntent(Intent intent)1522 public void addPreferencesFromIntent(Intent intent) { 1523 requirePreferenceManager(); 1524 1525 setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen())); 1526 } 1527 1528 /** 1529 * Inflates the given XML resource and adds the preference hierarchy to the current 1530 * preference hierarchy. 1531 * 1532 * @param preferencesResId The XML resource ID to inflate. 1533 * 1534 * @deprecated This function is not relevant for a modern fragment-based 1535 * PreferenceActivity. 1536 */ 1537 @Deprecated addPreferencesFromResource(int preferencesResId)1538 public void addPreferencesFromResource(int preferencesResId) { 1539 requirePreferenceManager(); 1540 1541 setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId, 1542 getPreferenceScreen())); 1543 } 1544 1545 /** 1546 * {@inheritDoc} 1547 * 1548 * @deprecated This function is not relevant for a modern fragment-based 1549 * PreferenceActivity. 1550 */ 1551 @Deprecated onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)1552 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { 1553 return false; 1554 } 1555 1556 /** 1557 * Finds a {@link Preference} based on its key. 1558 * 1559 * @param key The key of the preference to retrieve. 1560 * @return The {@link Preference} with the key, or null. 1561 * @see PreferenceGroup#findPreference(CharSequence) 1562 * 1563 * @deprecated This function is not relevant for a modern fragment-based 1564 * PreferenceActivity. 1565 */ 1566 @Deprecated findPreference(CharSequence key)1567 public Preference findPreference(CharSequence key) { 1568 1569 if (mPreferenceManager == null) { 1570 return null; 1571 } 1572 1573 return mPreferenceManager.findPreference(key); 1574 } 1575 1576 @Override onNewIntent(Intent intent)1577 protected void onNewIntent(Intent intent) { 1578 if (mPreferenceManager != null) { 1579 mPreferenceManager.dispatchNewIntent(intent); 1580 } 1581 } 1582 1583 // give subclasses access to the Next button 1584 /** @hide */ hasNextButton()1585 protected boolean hasNextButton() { 1586 return mNextButton != null; 1587 } 1588 /** @hide */ getNextButton()1589 protected Button getNextButton() { 1590 return mNextButton; 1591 } 1592 } 1593