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.pm.PackageManager; 27 import android.database.DataSetObserver; 28 import android.graphics.drawable.Drawable; 29 import android.os.Bundle; 30 import android.preference.Preference; 31 import android.preference.PreferenceActivity; 32 import android.preference.PreferenceFragment; 33 import android.preference.PreferenceGroupAdapter; 34 import android.text.TextUtils; 35 import android.util.Log; 36 import android.view.LayoutInflater; 37 import android.view.Menu; 38 import android.view.MenuInflater; 39 import android.view.MenuItem; 40 import android.view.View; 41 import android.view.ViewGroup; 42 import android.widget.Button; 43 import android.widget.ListAdapter; 44 import android.widget.ListView; 45 46 /** 47 * Base class for Settings fragments, with some helper functions and dialog management. 48 */ 49 public class SettingsPreferenceFragment extends PreferenceFragment implements DialogCreatable { 50 51 private static final String TAG = "SettingsPreferenceFragment"; 52 53 private static final int MENU_HELP = Menu.FIRST + 100; 54 private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600; 55 56 private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted"; 57 58 private SettingsDialogFragment mDialogFragment; 59 60 private String mHelpUrl; 61 62 // Cache the content resolver for async callbacks 63 private ContentResolver mContentResolver; 64 65 private String mPreferenceKey; 66 private boolean mPreferenceHighlighted = false; 67 private Drawable mHighlightDrawable; 68 69 private ListAdapter mCurrentRootAdapter; 70 private boolean mIsDataSetObserverRegistered = false; 71 private DataSetObserver mDataSetObserver = new DataSetObserver() { 72 @Override 73 public void onChanged() { 74 highlightPreferenceIfNeeded(); 75 } 76 77 @Override 78 public void onInvalidated() { 79 highlightPreferenceIfNeeded(); 80 } 81 }; 82 83 private ViewGroup mPinnedHeaderFrameLayout; 84 85 @Override onCreate(Bundle icicle)86 public void onCreate(Bundle icicle) { 87 super.onCreate(icicle); 88 89 if (icicle != null) { 90 mPreferenceHighlighted = icicle.getBoolean(SAVE_HIGHLIGHTED_KEY); 91 } 92 93 // Prepare help url and enable menu if necessary 94 int helpResource = getHelpResource(); 95 if (helpResource != 0) { 96 mHelpUrl = getResources().getString(helpResource); 97 } 98 } 99 100 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)101 public View onCreateView(LayoutInflater inflater, ViewGroup container, 102 Bundle savedInstanceState) { 103 final View root = super.onCreateView(inflater, container, savedInstanceState); 104 mPinnedHeaderFrameLayout = (ViewGroup) root.findViewById(R.id.pinned_header); 105 return root; 106 } 107 setPinnedHeaderView(View pinnedHeader)108 public void setPinnedHeaderView(View pinnedHeader) { 109 mPinnedHeaderFrameLayout.addView(pinnedHeader); 110 mPinnedHeaderFrameLayout.setVisibility(View.VISIBLE); 111 } 112 clearPinnedHeaderView()113 public void clearPinnedHeaderView() { 114 mPinnedHeaderFrameLayout.removeAllViews(); 115 mPinnedHeaderFrameLayout.setVisibility(View.GONE); 116 } 117 118 @Override onSaveInstanceState(Bundle outState)119 public void onSaveInstanceState(Bundle outState) { 120 super.onSaveInstanceState(outState); 121 122 outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted); 123 } 124 125 @Override onActivityCreated(Bundle savedInstanceState)126 public void onActivityCreated(Bundle savedInstanceState) { 127 super.onActivityCreated(savedInstanceState); 128 if (!TextUtils.isEmpty(mHelpUrl)) { 129 setHasOptionsMenu(true); 130 } 131 } 132 133 @Override onResume()134 public void onResume() { 135 super.onResume(); 136 137 final Bundle args = getArguments(); 138 if (args != null) { 139 mPreferenceKey = args.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY); 140 highlightPreferenceIfNeeded(); 141 } 142 } 143 144 @Override onBindPreferences()145 protected void onBindPreferences() { 146 registerObserverIfNeeded(); 147 } 148 149 @Override onUnbindPreferences()150 protected void onUnbindPreferences() { 151 unregisterObserverIfNeeded(); 152 } 153 154 @Override onStop()155 public void onStop() { 156 super.onStop(); 157 158 unregisterObserverIfNeeded(); 159 } 160 registerObserverIfNeeded()161 public void registerObserverIfNeeded() { 162 if (!mIsDataSetObserverRegistered) { 163 if (mCurrentRootAdapter != null) { 164 mCurrentRootAdapter.unregisterDataSetObserver(mDataSetObserver); 165 } 166 mCurrentRootAdapter = getPreferenceScreen().getRootAdapter(); 167 mCurrentRootAdapter.registerDataSetObserver(mDataSetObserver); 168 mIsDataSetObserverRegistered = true; 169 } 170 } 171 unregisterObserverIfNeeded()172 public void unregisterObserverIfNeeded() { 173 if (mIsDataSetObserverRegistered) { 174 if (mCurrentRootAdapter != null) { 175 mCurrentRootAdapter.unregisterDataSetObserver(mDataSetObserver); 176 mCurrentRootAdapter = null; 177 } 178 mIsDataSetObserverRegistered = false; 179 } 180 } 181 highlightPreferenceIfNeeded()182 public void highlightPreferenceIfNeeded() { 183 if (isAdded() && !mPreferenceHighlighted &&!TextUtils.isEmpty(mPreferenceKey)) { 184 highlightPreference(mPreferenceKey); 185 } 186 } 187 getHighlightDrawable()188 private Drawable getHighlightDrawable() { 189 if (mHighlightDrawable == null) { 190 mHighlightDrawable = getActivity().getDrawable(R.drawable.preference_highlight); 191 } 192 return mHighlightDrawable; 193 } 194 195 /** 196 * Return a valid ListView position or -1 if none is found 197 */ canUseListViewForHighLighting(String key)198 private int canUseListViewForHighLighting(String key) { 199 if (!hasListView()) { 200 return -1; 201 } 202 203 ListView listView = getListView(); 204 ListAdapter adapter = listView.getAdapter(); 205 206 if (adapter != null && adapter instanceof PreferenceGroupAdapter) { 207 return findListPositionFromKey(adapter, key); 208 } 209 210 return -1; 211 } 212 highlightPreference(String key)213 private void highlightPreference(String key) { 214 final Drawable highlight = getHighlightDrawable(); 215 216 final int position = canUseListViewForHighLighting(key); 217 if (position >= 0) { 218 mPreferenceHighlighted = true; 219 220 final ListView listView = getListView(); 221 final ListAdapter adapter = listView.getAdapter(); 222 223 ((PreferenceGroupAdapter) adapter).setHighlightedDrawable(highlight); 224 ((PreferenceGroupAdapter) adapter).setHighlighted(position); 225 226 listView.post(new Runnable() { 227 @Override 228 public void run() { 229 listView.setSelection(position); 230 listView.postDelayed(new Runnable() { 231 @Override 232 public void run() { 233 final int index = position - listView.getFirstVisiblePosition(); 234 if (index >= 0 && index < listView.getChildCount()) { 235 final View v = listView.getChildAt(index); 236 final int centerX = v.getWidth() / 2; 237 final int centerY = v.getHeight() / 2; 238 highlight.setHotspot(centerX, centerY); 239 v.setPressed(true); 240 v.setPressed(false); 241 } 242 } 243 }, DELAY_HIGHLIGHT_DURATION_MILLIS); 244 } 245 }); 246 } 247 } 248 findListPositionFromKey(ListAdapter adapter, String key)249 private int findListPositionFromKey(ListAdapter adapter, String key) { 250 final int count = adapter.getCount(); 251 for (int n = 0; n < count; n++) { 252 final Object item = adapter.getItem(n); 253 if (item instanceof Preference) { 254 Preference preference = (Preference) item; 255 final String preferenceKey = preference.getKey(); 256 if (preferenceKey != null && preferenceKey.equals(key)) { 257 return n; 258 } 259 } 260 } 261 return -1; 262 } 263 removePreference(String key)264 protected void removePreference(String key) { 265 Preference pref = findPreference(key); 266 if (pref != null) { 267 getPreferenceScreen().removePreference(pref); 268 } 269 } 270 271 /** 272 * Override this if you want to show a help item in the menu, by returning the resource id. 273 * @return the resource id for the help url 274 */ getHelpResource()275 protected int getHelpResource() { 276 return 0; 277 } 278 279 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)280 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 281 if (mHelpUrl != null && getActivity() != null) { 282 MenuItem helpItem = menu.add(0, MENU_HELP, 0, R.string.help_label); 283 HelpUtils.prepareHelpMenuItem(getActivity(), helpItem, mHelpUrl); 284 } 285 } 286 287 /* 288 * The name is intentionally made different from Activity#finish(), so that 289 * users won't misunderstand its meaning. 290 */ finishFragment()291 public final void finishFragment() { 292 getActivity().onBackPressed(); 293 } 294 295 // Some helpers for functions used by the settings fragments when they were activities 296 297 /** 298 * Returns the ContentResolver from the owning Activity. 299 */ getContentResolver()300 protected ContentResolver getContentResolver() { 301 Context context = getActivity(); 302 if (context != null) { 303 mContentResolver = context.getContentResolver(); 304 } 305 return mContentResolver; 306 } 307 308 /** 309 * Returns the specified system service from the owning Activity. 310 */ getSystemService(final String name)311 protected Object getSystemService(final String name) { 312 return getActivity().getSystemService(name); 313 } 314 315 /** 316 * Returns the PackageManager from the owning Activity. 317 */ getPackageManager()318 protected PackageManager getPackageManager() { 319 return getActivity().getPackageManager(); 320 } 321 322 @Override onDetach()323 public void onDetach() { 324 if (isRemoving()) { 325 if (mDialogFragment != null) { 326 mDialogFragment.dismiss(); 327 mDialogFragment = null; 328 } 329 } 330 super.onDetach(); 331 } 332 333 // Dialog management 334 showDialog(int dialogId)335 protected void showDialog(int dialogId) { 336 if (mDialogFragment != null) { 337 Log.e(TAG, "Old dialog fragment not null!"); 338 } 339 mDialogFragment = new SettingsDialogFragment(this, dialogId); 340 mDialogFragment.show(getChildFragmentManager(), Integer.toString(dialogId)); 341 } 342 onCreateDialog(int dialogId)343 public Dialog onCreateDialog(int dialogId) { 344 return null; 345 } 346 removeDialog(int dialogId)347 protected void removeDialog(int dialogId) { 348 // mDialogFragment may not be visible yet in parent fragment's onResume(). 349 // To be able to dismiss dialog at that time, don't check 350 // mDialogFragment.isVisible(). 351 if (mDialogFragment != null && mDialogFragment.getDialogId() == dialogId) { 352 mDialogFragment.dismiss(); 353 } 354 mDialogFragment = null; 355 } 356 357 /** 358 * Sets the OnCancelListener of the dialog shown. This method can only be 359 * called after showDialog(int) and before removeDialog(int). The method 360 * does nothing otherwise. 361 */ setOnCancelListener(DialogInterface.OnCancelListener listener)362 protected void setOnCancelListener(DialogInterface.OnCancelListener listener) { 363 if (mDialogFragment != null) { 364 mDialogFragment.mOnCancelListener = listener; 365 } 366 } 367 368 /** 369 * Sets the OnDismissListener of the dialog shown. This method can only be 370 * called after showDialog(int) and before removeDialog(int). The method 371 * does nothing otherwise. 372 */ setOnDismissListener(DialogInterface.OnDismissListener listener)373 protected void setOnDismissListener(DialogInterface.OnDismissListener listener) { 374 if (mDialogFragment != null) { 375 mDialogFragment.mOnDismissListener = listener; 376 } 377 } 378 onDialogShowing()379 public void onDialogShowing() { 380 // override in subclass to attach a dismiss listener, for instance 381 } 382 383 public static class SettingsDialogFragment extends DialogFragment { 384 private static final String KEY_DIALOG_ID = "key_dialog_id"; 385 private static final String KEY_PARENT_FRAGMENT_ID = "key_parent_fragment_id"; 386 387 private int mDialogId; 388 389 private Fragment mParentFragment; 390 391 private DialogInterface.OnCancelListener mOnCancelListener; 392 private DialogInterface.OnDismissListener mOnDismissListener; 393 SettingsDialogFragment()394 public SettingsDialogFragment() { 395 /* do nothing */ 396 } 397 SettingsDialogFragment(DialogCreatable fragment, int dialogId)398 public SettingsDialogFragment(DialogCreatable fragment, int dialogId) { 399 mDialogId = dialogId; 400 if (!(fragment instanceof Fragment)) { 401 throw new IllegalArgumentException("fragment argument must be an instance of " 402 + Fragment.class.getName()); 403 } 404 mParentFragment = (Fragment) fragment; 405 } 406 407 @Override onSaveInstanceState(Bundle outState)408 public void onSaveInstanceState(Bundle outState) { 409 super.onSaveInstanceState(outState); 410 if (mParentFragment != null) { 411 outState.putInt(KEY_DIALOG_ID, mDialogId); 412 outState.putInt(KEY_PARENT_FRAGMENT_ID, mParentFragment.getId()); 413 } 414 } 415 416 @Override onStart()417 public void onStart() { 418 super.onStart(); 419 420 if (mParentFragment != null && mParentFragment instanceof SettingsPreferenceFragment) { 421 ((SettingsPreferenceFragment) mParentFragment).onDialogShowing(); 422 } 423 } 424 425 @Override onCreateDialog(Bundle savedInstanceState)426 public Dialog onCreateDialog(Bundle savedInstanceState) { 427 if (savedInstanceState != null) { 428 mDialogId = savedInstanceState.getInt(KEY_DIALOG_ID, 0); 429 mParentFragment = getParentFragment(); 430 int mParentFragmentId = savedInstanceState.getInt(KEY_PARENT_FRAGMENT_ID, -1); 431 if (mParentFragment == null) { 432 mParentFragment = getFragmentManager().findFragmentById(mParentFragmentId); 433 } 434 if (!(mParentFragment instanceof DialogCreatable)) { 435 throw new IllegalArgumentException( 436 (mParentFragment != null 437 ? mParentFragment.getClass().getName() 438 : mParentFragmentId) 439 + " must implement " 440 + DialogCreatable.class.getName()); 441 } 442 // This dialog fragment could be created from non-SettingsPreferenceFragment 443 if (mParentFragment instanceof SettingsPreferenceFragment) { 444 // restore mDialogFragment in mParentFragment 445 ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = this; 446 } 447 } 448 return ((DialogCreatable) mParentFragment).onCreateDialog(mDialogId); 449 } 450 451 @Override onCancel(DialogInterface dialog)452 public void onCancel(DialogInterface dialog) { 453 super.onCancel(dialog); 454 if (mOnCancelListener != null) { 455 mOnCancelListener.onCancel(dialog); 456 } 457 } 458 459 @Override onDismiss(DialogInterface dialog)460 public void onDismiss(DialogInterface dialog) { 461 super.onDismiss(dialog); 462 if (mOnDismissListener != null) { 463 mOnDismissListener.onDismiss(dialog); 464 } 465 } 466 getDialogId()467 public int getDialogId() { 468 return mDialogId; 469 } 470 471 @Override onDetach()472 public void onDetach() { 473 super.onDetach(); 474 475 // This dialog fragment could be created from non-SettingsPreferenceFragment 476 if (mParentFragment instanceof SettingsPreferenceFragment) { 477 // in case the dialog is not explicitly removed by removeDialog() 478 if (((SettingsPreferenceFragment) mParentFragment).mDialogFragment == this) { 479 ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = null; 480 } 481 } 482 } 483 } 484 hasNextButton()485 protected boolean hasNextButton() { 486 return ((ButtonBarHandler)getActivity()).hasNextButton(); 487 } 488 getNextButton()489 protected Button getNextButton() { 490 return ((ButtonBarHandler)getActivity()).getNextButton(); 491 } 492 finish()493 public void finish() { 494 getActivity().onBackPressed(); 495 } 496 startFragment(Fragment caller, String fragmentClass, int titleRes, int requestCode, Bundle extras)497 public boolean startFragment(Fragment caller, String fragmentClass, int titleRes, 498 int requestCode, Bundle extras) { 499 final Activity activity = getActivity(); 500 if (activity instanceof SettingsActivity) { 501 SettingsActivity sa = (SettingsActivity) activity; 502 sa.startPreferencePanel(fragmentClass, extras, titleRes, null, caller, requestCode); 503 return true; 504 } else if (activity instanceof PreferenceActivity) { 505 PreferenceActivity sa = (PreferenceActivity) activity; 506 sa.startPreferencePanel(fragmentClass, extras, titleRes, null, caller, requestCode); 507 return true; 508 } else { 509 Log.w(TAG, 510 "Parent isn't SettingsActivity nor PreferenceActivity, thus there's no way to " 511 + "launch the given Fragment (name: " + fragmentClass 512 + ", requestCode: " + requestCode + ")"); 513 return false; 514 } 515 } 516 } 517