1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings; 18 19 import android.app.Activity; 20 import android.app.Dialog; 21 import android.app.DialogFragment; 22 import android.app.Fragment; 23 import android.content.ContentResolver; 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.content.Intent; 27 import android.content.pm.PackageManager; 28 import android.os.Bundle; 29 import android.support.annotation.VisibleForTesting; 30 import android.support.annotation.XmlRes; 31 import android.support.v7.preference.Preference; 32 import android.support.v7.preference.PreferenceGroup; 33 import android.support.v7.preference.PreferenceGroupAdapter; 34 import android.support.v7.preference.PreferenceScreen; 35 import android.support.v7.preference.PreferenceViewHolder; 36 import android.support.v7.widget.LinearLayoutManager; 37 import android.support.v7.widget.RecyclerView; 38 import android.text.TextUtils; 39 import android.util.ArrayMap; 40 import android.util.Log; 41 import android.view.LayoutInflater; 42 import android.view.Menu; 43 import android.view.MenuInflater; 44 import android.view.View; 45 import android.view.ViewGroup; 46 import android.widget.Button; 47 48 import com.android.settings.applications.LayoutPreference; 49 import com.android.settings.core.InstrumentedPreferenceFragment; 50 import com.android.settings.core.instrumentation.Instrumentable; 51 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 52 import com.android.settings.widget.LoadingViewController; 53 import com.android.settingslib.CustomDialogPreference; 54 import com.android.settingslib.CustomEditTextPreference; 55 import com.android.settingslib.HelpUtils; 56 import com.android.settingslib.widget.FooterPreferenceMixin; 57 58 import java.util.UUID; 59 60 /** 61 * Base class for Settings fragments, with some helper functions and dialog management. 62 */ 63 public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceFragment 64 implements DialogCreatable { 65 66 /** 67 * The Help Uri Resource key. This can be passed as an extra argument when creating the 68 * Fragment. 69 **/ 70 public static final String HELP_URI_RESOURCE_KEY = "help_uri_resource"; 71 72 private static final String TAG = "SettingsPreference"; 73 74 @VisibleForTesting 75 static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600; 76 77 private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted"; 78 79 protected final FooterPreferenceMixin mFooterPreferenceMixin = 80 new FooterPreferenceMixin(this, getLifecycle()); 81 82 private SettingsDialogFragment mDialogFragment; 83 84 private String mHelpUri; 85 86 private static final int ORDER_FIRST = -1; 87 private static final int ORDER_LAST = Integer.MAX_VALUE -1; 88 89 // Cache the content resolver for async callbacks 90 private ContentResolver mContentResolver; 91 92 private String mPreferenceKey; 93 94 private RecyclerView.Adapter mCurrentRootAdapter; 95 private boolean mIsDataSetObserverRegistered = false; 96 private RecyclerView.AdapterDataObserver mDataSetObserver = 97 new RecyclerView.AdapterDataObserver() { 98 @Override 99 public void onChanged() { 100 onDataSetChanged(); 101 } 102 103 @Override 104 public void onItemRangeChanged(int positionStart, int itemCount) { 105 onDataSetChanged(); 106 } 107 108 @Override 109 public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { 110 onDataSetChanged(); 111 } 112 113 @Override 114 public void onItemRangeInserted(int positionStart, int itemCount) { 115 onDataSetChanged(); 116 } 117 118 @Override 119 public void onItemRangeRemoved(int positionStart, int itemCount) { 120 onDataSetChanged(); 121 } 122 123 @Override 124 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 125 onDataSetChanged(); 126 } 127 }; 128 129 private ViewGroup mPinnedHeaderFrameLayout; 130 private ViewGroup mButtonBar; 131 132 private LayoutPreference mHeader; 133 134 private View mEmptyView; 135 private LinearLayoutManager mLayoutManager; 136 private ArrayMap<String, Preference> mPreferenceCache; 137 private boolean mAnimationAllowed; 138 139 @VisibleForTesting 140 public HighlightablePreferenceGroupAdapter mAdapter; 141 @VisibleForTesting 142 public boolean mPreferenceHighlighted = false; 143 144 @Override onCreate(Bundle icicle)145 public void onCreate(Bundle icicle) { 146 super.onCreate(icicle); 147 148 if (icicle != null) { 149 mPreferenceHighlighted = icicle.getBoolean(SAVE_HIGHLIGHTED_KEY); 150 } 151 152 // Prepare help url and enable menu if necessary 153 Bundle arguments = getArguments(); 154 int helpResource; 155 if (arguments != null && arguments.containsKey(HELP_URI_RESOURCE_KEY)) { 156 helpResource = arguments.getInt(HELP_URI_RESOURCE_KEY); 157 } else { 158 helpResource = getHelpResource(); 159 } 160 if (helpResource != 0) { 161 mHelpUri = getResources().getString(helpResource); 162 } 163 } 164 165 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)166 public View onCreateView(LayoutInflater inflater, ViewGroup container, 167 Bundle savedInstanceState) { 168 final View root = super.onCreateView(inflater, container, savedInstanceState); 169 mPinnedHeaderFrameLayout = (ViewGroup) root.findViewById(R.id.pinned_header); 170 mButtonBar = (ViewGroup) root.findViewById(R.id.button_bar); 171 return root; 172 } 173 174 @Override addPreferencesFromResource(@mlRes int preferencesResId)175 public void addPreferencesFromResource(@XmlRes int preferencesResId) { 176 super.addPreferencesFromResource(preferencesResId); 177 checkAvailablePrefs(getPreferenceScreen()); 178 } 179 checkAvailablePrefs(PreferenceGroup preferenceGroup)180 private void checkAvailablePrefs(PreferenceGroup preferenceGroup) { 181 if (preferenceGroup == null) return; 182 for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) { 183 Preference pref = preferenceGroup.getPreference(i); 184 if (pref instanceof SelfAvailablePreference 185 && !((SelfAvailablePreference) pref).isAvailable(getContext())) { 186 preferenceGroup.removePreference(pref); 187 } else if (pref instanceof PreferenceGroup) { 188 checkAvailablePrefs((PreferenceGroup) pref); 189 } 190 } 191 } 192 getButtonBar()193 public ViewGroup getButtonBar() { 194 return mButtonBar; 195 } 196 setPinnedHeaderView(int layoutResId)197 public View setPinnedHeaderView(int layoutResId) { 198 final LayoutInflater inflater = getActivity().getLayoutInflater(); 199 final View pinnedHeader = 200 inflater.inflate(layoutResId, mPinnedHeaderFrameLayout, false); 201 setPinnedHeaderView(pinnedHeader); 202 return pinnedHeader; 203 } 204 setPinnedHeaderView(View pinnedHeader)205 public void setPinnedHeaderView(View pinnedHeader) { 206 mPinnedHeaderFrameLayout.addView(pinnedHeader); 207 mPinnedHeaderFrameLayout.setVisibility(View.VISIBLE); 208 } 209 210 @Override onSaveInstanceState(Bundle outState)211 public void onSaveInstanceState(Bundle outState) { 212 super.onSaveInstanceState(outState); 213 214 outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted); 215 } 216 217 @Override onActivityCreated(Bundle savedInstanceState)218 public void onActivityCreated(Bundle savedInstanceState) { 219 super.onActivityCreated(savedInstanceState); 220 setHasOptionsMenu(true); 221 } 222 223 @Override onResume()224 public void onResume() { 225 super.onResume(); 226 227 final Bundle args = getArguments(); 228 if (args != null) { 229 mPreferenceKey = args.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY); 230 highlightPreferenceIfNeeded(); 231 } 232 } 233 234 @Override onBindPreferences()235 protected void onBindPreferences() { 236 registerObserverIfNeeded(); 237 } 238 239 @Override onUnbindPreferences()240 protected void onUnbindPreferences() { 241 unregisterObserverIfNeeded(); 242 } 243 setLoading(boolean loading, boolean animate)244 public void setLoading(boolean loading, boolean animate) { 245 View loadingContainer = getView().findViewById(R.id.loading_container); 246 LoadingViewController.handleLoadingContainer(loadingContainer, getListView(), 247 !loading /* done */, 248 animate); 249 } 250 registerObserverIfNeeded()251 public void registerObserverIfNeeded() { 252 if (!mIsDataSetObserverRegistered) { 253 if (mCurrentRootAdapter != null) { 254 mCurrentRootAdapter.unregisterAdapterDataObserver(mDataSetObserver); 255 } 256 mCurrentRootAdapter = getListView().getAdapter(); 257 mCurrentRootAdapter.registerAdapterDataObserver(mDataSetObserver); 258 mIsDataSetObserverRegistered = true; 259 onDataSetChanged(); 260 } 261 } 262 unregisterObserverIfNeeded()263 public void unregisterObserverIfNeeded() { 264 if (mIsDataSetObserverRegistered) { 265 if (mCurrentRootAdapter != null) { 266 mCurrentRootAdapter.unregisterAdapterDataObserver(mDataSetObserver); 267 mCurrentRootAdapter = null; 268 } 269 mIsDataSetObserverRegistered = false; 270 } 271 } 272 highlightPreferenceIfNeeded()273 public void highlightPreferenceIfNeeded() { 274 if (isAdded() && !mPreferenceHighlighted &&!TextUtils.isEmpty(mPreferenceKey)) { 275 getView().postDelayed(new Runnable() { 276 @Override 277 public void run() { 278 highlightPreference(mPreferenceKey); 279 } 280 }, DELAY_HIGHLIGHT_DURATION_MILLIS); 281 } 282 } 283 onDataSetChanged()284 protected void onDataSetChanged() { 285 highlightPreferenceIfNeeded(); 286 updateEmptyView(); 287 } 288 getHeaderView()289 public LayoutPreference getHeaderView() { 290 return mHeader; 291 } 292 setHeaderView(int resource)293 protected void setHeaderView(int resource) { 294 mHeader = new LayoutPreference(getPrefContext(), resource); 295 addPreferenceToTop(mHeader); 296 } 297 setHeaderView(View view)298 protected void setHeaderView(View view) { 299 mHeader = new LayoutPreference(getPrefContext(), view); 300 addPreferenceToTop(mHeader); 301 } 302 addPreferenceToTop(LayoutPreference preference)303 private void addPreferenceToTop(LayoutPreference preference) { 304 preference.setOrder(ORDER_FIRST); 305 if (getPreferenceScreen() != null) { 306 getPreferenceScreen().addPreference(preference); 307 } 308 } 309 310 @Override setPreferenceScreen(PreferenceScreen preferenceScreen)311 public void setPreferenceScreen(PreferenceScreen preferenceScreen) { 312 if (preferenceScreen != null && !preferenceScreen.isAttached()) { 313 // Without ids generated, the RecyclerView won't animate changes to the preferences. 314 preferenceScreen.setShouldUseGeneratedIds(mAnimationAllowed); 315 } 316 super.setPreferenceScreen(preferenceScreen); 317 if (preferenceScreen != null) { 318 if (mHeader != null) { 319 preferenceScreen.addPreference(mHeader); 320 } 321 } 322 } 323 324 @VisibleForTesting updateEmptyView()325 void updateEmptyView() { 326 if (mEmptyView == null) return; 327 if (getPreferenceScreen() != null) { 328 final View listContainer = getActivity().findViewById(android.R.id.list_container); 329 boolean show = (getPreferenceScreen().getPreferenceCount() 330 - (mHeader != null ? 1 : 0) 331 - (mFooterPreferenceMixin.hasFooter() ? 1 : 0)) <= 0 332 || (listContainer != null && listContainer.getVisibility() != View.VISIBLE); 333 mEmptyView.setVisibility(show ? View.VISIBLE : View.GONE); 334 } else { 335 mEmptyView.setVisibility(View.VISIBLE); 336 } 337 } 338 setEmptyView(View v)339 public void setEmptyView(View v) { 340 if (mEmptyView != null) { 341 mEmptyView.setVisibility(View.GONE); 342 } 343 mEmptyView = v; 344 updateEmptyView(); 345 } 346 getEmptyView()347 public View getEmptyView() { 348 return mEmptyView; 349 } 350 351 /** 352 * Return a valid ListView position or -1 if none is found 353 */ canUseListViewForHighLighting(String key)354 private int canUseListViewForHighLighting(String key) { 355 if (getListView() == null) { 356 return -1; 357 } 358 359 RecyclerView listView = getListView(); 360 RecyclerView.Adapter adapter = listView.getAdapter(); 361 362 if (adapter != null && adapter instanceof PreferenceGroupAdapter) { 363 return findListPositionFromKey((PreferenceGroupAdapter) adapter, key); 364 } 365 366 return -1; 367 } 368 369 @Override onCreateLayoutManager()370 public RecyclerView.LayoutManager onCreateLayoutManager() { 371 mLayoutManager = new LinearLayoutManager(getContext()); 372 return mLayoutManager; 373 } 374 375 @Override onCreateAdapter(PreferenceScreen preferenceScreen)376 protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { 377 mAdapter = new HighlightablePreferenceGroupAdapter(preferenceScreen); 378 return mAdapter; 379 } 380 setAnimationAllowed(boolean animationAllowed)381 protected void setAnimationAllowed(boolean animationAllowed) { 382 mAnimationAllowed = animationAllowed; 383 } 384 cacheRemoveAllPrefs(PreferenceGroup group)385 protected void cacheRemoveAllPrefs(PreferenceGroup group) { 386 mPreferenceCache = new ArrayMap<String, Preference>(); 387 final int N = group.getPreferenceCount(); 388 for (int i = 0; i < N; i++) { 389 Preference p = group.getPreference(i); 390 if (TextUtils.isEmpty(p.getKey())) { 391 continue; 392 } 393 mPreferenceCache.put(p.getKey(), p); 394 } 395 } 396 getCachedPreference(String key)397 protected Preference getCachedPreference(String key) { 398 return mPreferenceCache != null ? mPreferenceCache.remove(key) : null; 399 } 400 removeCachedPrefs(PreferenceGroup group)401 protected void removeCachedPrefs(PreferenceGroup group) { 402 for (Preference p : mPreferenceCache.values()) { 403 group.removePreference(p); 404 } 405 mPreferenceCache = null; 406 } 407 getCachedCount()408 protected int getCachedCount() { 409 return mPreferenceCache != null ? mPreferenceCache.size() : 0; 410 } 411 highlightPreference(String key)412 private void highlightPreference(String key) { 413 final int position = canUseListViewForHighLighting(key); 414 if (position < 0) { 415 return; 416 } 417 418 mPreferenceHighlighted = true; 419 mLayoutManager.scrollToPosition(position); 420 mAdapter.highlight(position); 421 } 422 findListPositionFromKey(PreferenceGroupAdapter adapter, String key)423 private int findListPositionFromKey(PreferenceGroupAdapter adapter, String key) { 424 final int count = adapter.getItemCount(); 425 for (int n = 0; n < count; n++) { 426 final Preference preference = adapter.getItem(n); 427 final String preferenceKey = preference.getKey(); 428 if (preferenceKey != null && preferenceKey.equals(key)) { 429 return n; 430 } 431 } 432 return -1; 433 } 434 removePreference(String key)435 protected boolean removePreference(String key) { 436 return removePreference(getPreferenceScreen(), key); 437 } 438 439 @VisibleForTesting removePreference(PreferenceGroup group, String key)440 boolean removePreference(PreferenceGroup group, String key) { 441 final int preferenceCount = group.getPreferenceCount(); 442 for (int i = 0; i < preferenceCount; i++) { 443 final Preference preference = group.getPreference(i); 444 final String curKey = preference.getKey(); 445 446 if (TextUtils.equals(curKey, key)) { 447 return group.removePreference(preference); 448 } 449 450 if (preference instanceof PreferenceGroup) { 451 if (removePreference((PreferenceGroup) preference, key)) { 452 return true; 453 } 454 } 455 } 456 return false; 457 } 458 459 /** 460 * Override this if you want to show a help item in the menu, by returning the resource id. 461 * @return the resource id for the help url 462 */ getHelpResource()463 protected int getHelpResource() { 464 return R.string.help_uri_default; 465 } 466 467 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)468 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 469 super.onCreateOptionsMenu(menu, inflater); 470 if (mHelpUri != null && getActivity() != null) { 471 HelpUtils.prepareHelpMenuItem(getActivity(), menu, mHelpUri, getClass().getName()); 472 } 473 } 474 475 /* 476 * The name is intentionally made different from Activity#finish(), so that 477 * users won't misunderstand its meaning. 478 */ finishFragment()479 public final void finishFragment() { 480 getActivity().onBackPressed(); 481 } 482 483 // Some helpers for functions used by the settings fragments when they were activities 484 485 /** 486 * Returns the ContentResolver from the owning Activity. 487 */ getContentResolver()488 protected ContentResolver getContentResolver() { 489 Context context = getActivity(); 490 if (context != null) { 491 mContentResolver = context.getContentResolver(); 492 } 493 return mContentResolver; 494 } 495 496 /** 497 * Returns the specified system service from the owning Activity. 498 */ getSystemService(final String name)499 protected Object getSystemService(final String name) { 500 return getActivity().getSystemService(name); 501 } 502 503 /** 504 * Returns the PackageManager from the owning Activity. 505 */ getPackageManager()506 protected PackageManager getPackageManager() { 507 return getActivity().getPackageManager(); 508 } 509 510 @Override onDetach()511 public void onDetach() { 512 if (isRemoving()) { 513 if (mDialogFragment != null) { 514 mDialogFragment.dismiss(); 515 mDialogFragment = null; 516 } 517 } 518 super.onDetach(); 519 } 520 521 // Dialog management 522 showDialog(int dialogId)523 protected void showDialog(int dialogId) { 524 if (mDialogFragment != null) { 525 Log.e(TAG, "Old dialog fragment not null!"); 526 } 527 mDialogFragment = new SettingsDialogFragment(this, dialogId); 528 mDialogFragment.show(getChildFragmentManager(), Integer.toString(dialogId)); 529 } 530 531 @Override onCreateDialog(int dialogId)532 public Dialog onCreateDialog(int dialogId) { 533 return null; 534 } 535 536 @Override getDialogMetricsCategory(int dialogId)537 public int getDialogMetricsCategory(int dialogId) { 538 return 0; 539 } 540 removeDialog(int dialogId)541 protected void removeDialog(int dialogId) { 542 // mDialogFragment may not be visible yet in parent fragment's onResume(). 543 // To be able to dismiss dialog at that time, don't check 544 // mDialogFragment.isVisible(). 545 if (mDialogFragment != null && mDialogFragment.getDialogId() == dialogId) { 546 mDialogFragment.dismissAllowingStateLoss(); 547 } 548 mDialogFragment = null; 549 } 550 551 /** 552 * Sets the OnCancelListener of the dialog shown. This method can only be 553 * called after showDialog(int) and before removeDialog(int). The method 554 * does nothing otherwise. 555 */ setOnCancelListener(DialogInterface.OnCancelListener listener)556 protected void setOnCancelListener(DialogInterface.OnCancelListener listener) { 557 if (mDialogFragment != null) { 558 mDialogFragment.mOnCancelListener = listener; 559 } 560 } 561 562 /** 563 * Sets the OnDismissListener of the dialog shown. This method can only be 564 * called after showDialog(int) and before removeDialog(int). The method 565 * does nothing otherwise. 566 */ setOnDismissListener(DialogInterface.OnDismissListener listener)567 protected void setOnDismissListener(DialogInterface.OnDismissListener listener) { 568 if (mDialogFragment != null) { 569 mDialogFragment.mOnDismissListener = listener; 570 } 571 } 572 onDialogShowing()573 public void onDialogShowing() { 574 // override in subclass to attach a dismiss listener, for instance 575 } 576 577 @Override onDisplayPreferenceDialog(Preference preference)578 public void onDisplayPreferenceDialog(Preference preference) { 579 if (preference.getKey() == null) { 580 // Auto-key preferences that don't have a key, so the dialog can find them. 581 preference.setKey(UUID.randomUUID().toString()); 582 } 583 DialogFragment f = null; 584 if (preference instanceof RestrictedListPreference) { 585 f = RestrictedListPreference.RestrictedListPreferenceDialogFragment 586 .newInstance(preference.getKey()); 587 } else if (preference instanceof CustomListPreference) { 588 f = CustomListPreference.CustomListPreferenceDialogFragment 589 .newInstance(preference.getKey()); 590 } else if (preference instanceof CustomDialogPreference) { 591 f = CustomDialogPreference.CustomPreferenceDialogFragment 592 .newInstance(preference.getKey()); 593 } else if (preference instanceof CustomEditTextPreference) { 594 f = CustomEditTextPreference.CustomPreferenceDialogFragment 595 .newInstance(preference.getKey()); 596 } else { 597 super.onDisplayPreferenceDialog(preference); 598 return; 599 } 600 f.setTargetFragment(this, 0); 601 f.show(getFragmentManager(), "dialog_preference"); 602 onDialogShowing(); 603 } 604 605 public static class SettingsDialogFragment extends InstrumentedDialogFragment { 606 private static final String KEY_DIALOG_ID = "key_dialog_id"; 607 private static final String KEY_PARENT_FRAGMENT_ID = "key_parent_fragment_id"; 608 609 private Fragment mParentFragment; 610 611 private DialogInterface.OnCancelListener mOnCancelListener; 612 private DialogInterface.OnDismissListener mOnDismissListener; 613 SettingsDialogFragment()614 public SettingsDialogFragment() { 615 /* do nothing */ 616 } 617 SettingsDialogFragment(DialogCreatable fragment, int dialogId)618 public SettingsDialogFragment(DialogCreatable fragment, int dialogId) { 619 super(fragment, dialogId); 620 if (!(fragment instanceof Fragment)) { 621 throw new IllegalArgumentException("fragment argument must be an instance of " 622 + Fragment.class.getName()); 623 } 624 mParentFragment = (Fragment) fragment; 625 } 626 627 628 @Override getMetricsCategory()629 public int getMetricsCategory() { 630 if (mDialogCreatable == null) { 631 return Instrumentable.METRICS_CATEGORY_UNKNOWN; 632 } 633 final int metricsCategory = mDialogCreatable.getDialogMetricsCategory(mDialogId); 634 if (metricsCategory <= 0) { 635 throw new IllegalStateException("Dialog must provide a metrics category"); 636 } 637 return metricsCategory; 638 } 639 640 @Override onSaveInstanceState(Bundle outState)641 public void onSaveInstanceState(Bundle outState) { 642 super.onSaveInstanceState(outState); 643 if (mParentFragment != null) { 644 outState.putInt(KEY_DIALOG_ID, mDialogId); 645 outState.putInt(KEY_PARENT_FRAGMENT_ID, mParentFragment.getId()); 646 } 647 } 648 649 @Override onStart()650 public void onStart() { 651 super.onStart(); 652 653 if (mParentFragment != null && mParentFragment instanceof SettingsPreferenceFragment) { 654 ((SettingsPreferenceFragment) mParentFragment).onDialogShowing(); 655 } 656 } 657 658 @Override onCreateDialog(Bundle savedInstanceState)659 public Dialog onCreateDialog(Bundle savedInstanceState) { 660 if (savedInstanceState != null) { 661 mDialogId = savedInstanceState.getInt(KEY_DIALOG_ID, 0); 662 mParentFragment = getParentFragment(); 663 int mParentFragmentId = savedInstanceState.getInt(KEY_PARENT_FRAGMENT_ID, -1); 664 if (mParentFragment == null) { 665 mParentFragment = getFragmentManager().findFragmentById(mParentFragmentId); 666 } 667 if (!(mParentFragment instanceof DialogCreatable)) { 668 throw new IllegalArgumentException( 669 (mParentFragment != null 670 ? mParentFragment.getClass().getName() 671 : mParentFragmentId) 672 + " must implement " 673 + DialogCreatable.class.getName()); 674 } 675 // This dialog fragment could be created from non-SettingsPreferenceFragment 676 if (mParentFragment instanceof SettingsPreferenceFragment) { 677 // restore mDialogFragment in mParentFragment 678 ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = this; 679 } 680 } 681 return ((DialogCreatable) mParentFragment).onCreateDialog(mDialogId); 682 } 683 684 @Override onCancel(DialogInterface dialog)685 public void onCancel(DialogInterface dialog) { 686 super.onCancel(dialog); 687 if (mOnCancelListener != null) { 688 mOnCancelListener.onCancel(dialog); 689 } 690 } 691 692 @Override onDismiss(DialogInterface dialog)693 public void onDismiss(DialogInterface dialog) { 694 super.onDismiss(dialog); 695 if (mOnDismissListener != null) { 696 mOnDismissListener.onDismiss(dialog); 697 } 698 } 699 getDialogId()700 public int getDialogId() { 701 return mDialogId; 702 } 703 704 @Override onDetach()705 public void onDetach() { 706 super.onDetach(); 707 708 // This dialog fragment could be created from non-SettingsPreferenceFragment 709 if (mParentFragment instanceof SettingsPreferenceFragment) { 710 // in case the dialog is not explicitly removed by removeDialog() 711 if (((SettingsPreferenceFragment) mParentFragment).mDialogFragment == this) { 712 ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = null; 713 } 714 } 715 } 716 } 717 hasNextButton()718 protected boolean hasNextButton() { 719 return ((ButtonBarHandler)getActivity()).hasNextButton(); 720 } 721 getNextButton()722 protected Button getNextButton() { 723 return ((ButtonBarHandler)getActivity()).getNextButton(); 724 } 725 finish()726 public void finish() { 727 Activity activity = getActivity(); 728 if (activity == null) return; 729 if (getFragmentManager().getBackStackEntryCount() > 0) { 730 getFragmentManager().popBackStack(); 731 } else { 732 activity.finish(); 733 } 734 } 735 getIntent()736 protected Intent getIntent() { 737 if (getActivity() == null) { 738 return null; 739 } 740 return getActivity().getIntent(); 741 } 742 setResult(int result, Intent intent)743 protected void setResult(int result, Intent intent) { 744 if (getActivity() == null) { 745 return; 746 } 747 getActivity().setResult(result, intent); 748 } 749 setResult(int result)750 protected void setResult(int result) { 751 if (getActivity() == null) { 752 return; 753 } 754 getActivity().setResult(result); 755 } 756 startFragment(Fragment caller, String fragmentClass, int titleRes, int requestCode, Bundle extras)757 public boolean startFragment(Fragment caller, String fragmentClass, int titleRes, 758 int requestCode, Bundle extras) { 759 final Activity activity = getActivity(); 760 if (activity instanceof SettingsActivity) { 761 SettingsActivity sa = (SettingsActivity) activity; 762 sa.startPreferencePanel( 763 caller, fragmentClass, extras, titleRes, null, caller, requestCode); 764 return true; 765 } else { 766 Log.w(TAG, 767 "Parent isn't SettingsActivity nor PreferenceActivity, thus there's no way to " 768 + "launch the given Fragment (name: " + fragmentClass 769 + ", requestCode: " + requestCode + ")"); 770 return false; 771 } 772 } 773 774 public static class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter { 775 776 @VisibleForTesting(otherwise=VisibleForTesting.NONE) 777 int initialHighlightedPosition = -1; 778 779 private int mHighlightPosition = -1; 780 HighlightablePreferenceGroupAdapter(PreferenceGroup preferenceGroup)781 public HighlightablePreferenceGroupAdapter(PreferenceGroup preferenceGroup) { 782 super(preferenceGroup); 783 } 784 highlight(int position)785 public void highlight(int position) { 786 mHighlightPosition = position; 787 initialHighlightedPosition = position; 788 notifyDataSetChanged(); 789 } 790 791 @Override onBindViewHolder(PreferenceViewHolder holder, int position)792 public void onBindViewHolder(PreferenceViewHolder holder, int position) { 793 super.onBindViewHolder(holder, position); 794 if (position == mHighlightPosition) { 795 View v = holder.itemView; 796 v.post(() -> { 797 if (v.getBackground() != null) { 798 final int centerX = v.getWidth() / 2; 799 final int centerY = v.getHeight() / 2; 800 v.getBackground().setHotspot(centerX, centerY); 801 } 802 v.setPressed(true); 803 v.setPressed(false); 804 mHighlightPosition = -1; 805 }); 806 } 807 } 808 } 809 } 810