SettingsPreferenceFragment.java revision 906ff6fdf989ad4006c74d2bf37fc0e5d4da05f8
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 17package com.android.settings; 18 19import android.app.Dialog; 20import android.app.DialogFragment; 21import android.app.Fragment; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.DialogInterface; 25import android.content.pm.PackageManager; 26import android.graphics.drawable.Drawable; 27import android.os.Bundle; 28import android.preference.Preference; 29import android.preference.PreferenceFragment; 30import android.preference.PreferenceGroupAdapter; 31import android.text.TextUtils; 32import android.util.Log; 33import android.view.Menu; 34import android.view.MenuInflater; 35import android.view.MenuItem; 36import android.widget.Button; 37import android.widget.ListAdapter; 38import android.widget.ListView; 39 40/** 41 * Base class for Settings fragments, with some helper functions and dialog management. 42 */ 43public class SettingsPreferenceFragment extends PreferenceFragment implements DialogCreatable { 44 45 private static final String TAG = "SettingsPreferenceFragment"; 46 47 private static final int MENU_HELP = Menu.FIRST + 100; 48 private static final int HIGHLIGHT_DURATION_MILLIS = 300; 49 50 private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted"; 51 52 private SettingsDialogFragment mDialogFragment; 53 54 private String mHelpUrl; 55 56 // Cache the content resolver for async callbacks 57 private ContentResolver mContentResolver; 58 59 private boolean mPreferenceHighlighted = false; 60 61 @Override 62 public void onCreate(Bundle icicle) { 63 super.onCreate(icicle); 64 65 if (icicle != null) { 66 mPreferenceHighlighted = icicle.getBoolean(SAVE_HIGHLIGHTED_KEY); 67 } 68 69 // Prepare help url and enable menu if necessary 70 int helpResource = getHelpResource(); 71 if (helpResource != 0) { 72 mHelpUrl = getResources().getString(helpResource); 73 } 74 } 75 76 @Override 77 public void onSaveInstanceState(Bundle outState) { 78 super.onSaveInstanceState(outState); 79 80 outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted); 81 } 82 83 @Override 84 public void onActivityCreated(Bundle savedInstanceState) { 85 super.onActivityCreated(savedInstanceState); 86 if (!TextUtils.isEmpty(mHelpUrl)) { 87 setHasOptionsMenu(true); 88 } 89 90 final Bundle args = getArguments(); 91 if (args != null) { 92 final String key = args.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY); 93 if (hasListView() && !TextUtils.isEmpty(key)) { 94 highlightPreference(key); 95 } 96 } 97 } 98 99 private void highlightPreference(String key) { 100 final int position = findPositionFromKey(key); 101 if (position >= 0) { 102 final ListView listView = getListView(); 103 final ListAdapter adapter = listView.getAdapter(); 104 105 if (adapter instanceof PreferenceGroupAdapter) { 106 final Drawable drawable = getHighlightDrawable(); 107 ((PreferenceGroupAdapter) adapter).setHighlightedDrawable(drawable); 108 ((PreferenceGroupAdapter) adapter).setHighlighted(position); 109 110 listView.post(new Runnable() { 111 @Override 112 public void run() { 113 listView.setSelection(position); 114 if (!mPreferenceHighlighted) { 115 listView.postDelayed(new Runnable() { 116 @Override 117 public void run() { 118 final int centerX = listView.getWidth() / 2; 119 final int centerY = listView.getChildAt(0).getHeight() / 2; 120 drawable.setHotspot(0, centerX, centerY); 121 drawable.clearHotspots(); 122 ((PreferenceGroupAdapter) adapter).setHighlighted(-1); 123 } 124 }, HIGHLIGHT_DURATION_MILLIS); 125 126 mPreferenceHighlighted = true; 127 } 128 } 129 }); 130 } 131 } 132 133 } 134 135 private Drawable getHighlightDrawable() { 136 return getResources().getDrawable(R.drawable.preference_highlight); 137 } 138 139 private int findPositionFromKey(String key) { 140 final ListAdapter adapter = getListView().getAdapter(); 141 final int count = adapter.getCount(); 142 for (int n = 0; n < count; n++) { 143 Object item = adapter.getItem(n); 144 if (item instanceof Preference) { 145 Preference preference = (Preference) item; 146 final String preferenceKey = preference.getKey(); 147 if (preferenceKey != null && preferenceKey.equals(key)) { 148 return n; 149 } 150 } 151 } 152 return -1; 153 } 154 155 protected void removePreference(String key) { 156 Preference pref = findPreference(key); 157 if (pref != null) { 158 getPreferenceScreen().removePreference(pref); 159 } 160 } 161 162 /** 163 * Override this if you want to show a help item in the menu, by returning the resource id. 164 * @return the resource id for the help url 165 */ 166 protected int getHelpResource() { 167 return 0; 168 } 169 170 @Override 171 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 172 if (mHelpUrl != null && getActivity() != null) { 173 MenuItem helpItem = menu.add(0, MENU_HELP, 0, R.string.help_label); 174 HelpUtils.prepareHelpMenuItem(getActivity(), helpItem, mHelpUrl); 175 } 176 } 177 178 /* 179 * The name is intentionally made different from Activity#finish(), so that 180 * users won't misunderstand its meaning. 181 */ 182 public final void finishFragment() { 183 getActivity().onBackPressed(); 184 } 185 186 // Some helpers for functions used by the settings fragments when they were activities 187 188 /** 189 * Returns the ContentResolver from the owning Activity. 190 */ 191 protected ContentResolver getContentResolver() { 192 Context context = getActivity(); 193 if (context != null) { 194 mContentResolver = context.getContentResolver(); 195 } 196 return mContentResolver; 197 } 198 199 /** 200 * Returns the specified system service from the owning Activity. 201 */ 202 protected Object getSystemService(final String name) { 203 return getActivity().getSystemService(name); 204 } 205 206 /** 207 * Returns the PackageManager from the owning Activity. 208 */ 209 protected PackageManager getPackageManager() { 210 return getActivity().getPackageManager(); 211 } 212 213 @Override 214 public void onDetach() { 215 if (isRemoving()) { 216 if (mDialogFragment != null) { 217 mDialogFragment.dismiss(); 218 mDialogFragment = null; 219 } 220 } 221 super.onDetach(); 222 } 223 224 // Dialog management 225 226 protected void showDialog(int dialogId) { 227 if (mDialogFragment != null) { 228 Log.e(TAG, "Old dialog fragment not null!"); 229 } 230 mDialogFragment = new SettingsDialogFragment(this, dialogId); 231 mDialogFragment.show(getChildFragmentManager(), Integer.toString(dialogId)); 232 } 233 234 public Dialog onCreateDialog(int dialogId) { 235 return null; 236 } 237 238 protected void removeDialog(int dialogId) { 239 // mDialogFragment may not be visible yet in parent fragment's onResume(). 240 // To be able to dismiss dialog at that time, don't check 241 // mDialogFragment.isVisible(). 242 if (mDialogFragment != null && mDialogFragment.getDialogId() == dialogId) { 243 mDialogFragment.dismiss(); 244 } 245 mDialogFragment = null; 246 } 247 248 /** 249 * Sets the OnCancelListener of the dialog shown. This method can only be 250 * called after showDialog(int) and before removeDialog(int). The method 251 * does nothing otherwise. 252 */ 253 protected void setOnCancelListener(DialogInterface.OnCancelListener listener) { 254 if (mDialogFragment != null) { 255 mDialogFragment.mOnCancelListener = listener; 256 } 257 } 258 259 /** 260 * Sets the OnDismissListener of the dialog shown. This method can only be 261 * called after showDialog(int) and before removeDialog(int). The method 262 * does nothing otherwise. 263 */ 264 protected void setOnDismissListener(DialogInterface.OnDismissListener listener) { 265 if (mDialogFragment != null) { 266 mDialogFragment.mOnDismissListener = listener; 267 } 268 } 269 270 public void onDialogShowing() { 271 // override in subclass to attach a dismiss listener, for instance 272 } 273 274 public static class SettingsDialogFragment extends DialogFragment { 275 private static final String KEY_DIALOG_ID = "key_dialog_id"; 276 private static final String KEY_PARENT_FRAGMENT_ID = "key_parent_fragment_id"; 277 278 private int mDialogId; 279 280 private Fragment mParentFragment; 281 282 private DialogInterface.OnCancelListener mOnCancelListener; 283 private DialogInterface.OnDismissListener mOnDismissListener; 284 285 public SettingsDialogFragment() { 286 /* do nothing */ 287 } 288 289 public SettingsDialogFragment(DialogCreatable fragment, int dialogId) { 290 mDialogId = dialogId; 291 if (!(fragment instanceof Fragment)) { 292 throw new IllegalArgumentException("fragment argument must be an instance of " 293 + Fragment.class.getName()); 294 } 295 mParentFragment = (Fragment) fragment; 296 } 297 298 @Override 299 public void onSaveInstanceState(Bundle outState) { 300 super.onSaveInstanceState(outState); 301 if (mParentFragment != null) { 302 outState.putInt(KEY_DIALOG_ID, mDialogId); 303 outState.putInt(KEY_PARENT_FRAGMENT_ID, mParentFragment.getId()); 304 } 305 } 306 307 @Override 308 public void onStart() { 309 super.onStart(); 310 311 if (mParentFragment != null && mParentFragment instanceof SettingsPreferenceFragment) { 312 ((SettingsPreferenceFragment) mParentFragment).onDialogShowing(); 313 } 314 } 315 316 @Override 317 public Dialog onCreateDialog(Bundle savedInstanceState) { 318 if (savedInstanceState != null) { 319 mDialogId = savedInstanceState.getInt(KEY_DIALOG_ID, 0); 320 mParentFragment = getParentFragment(); 321 int mParentFragmentId = savedInstanceState.getInt(KEY_PARENT_FRAGMENT_ID, -1); 322 if (!(mParentFragment instanceof DialogCreatable)) { 323 throw new IllegalArgumentException( 324 (mParentFragment != null 325 ? mParentFragment.getClass().getName() 326 : mParentFragmentId) 327 + " must implement " 328 + DialogCreatable.class.getName()); 329 } 330 // This dialog fragment could be created from non-SettingsPreferenceFragment 331 if (mParentFragment instanceof SettingsPreferenceFragment) { 332 // restore mDialogFragment in mParentFragment 333 ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = this; 334 } 335 } 336 return ((DialogCreatable) mParentFragment).onCreateDialog(mDialogId); 337 } 338 339 @Override 340 public void onCancel(DialogInterface dialog) { 341 super.onCancel(dialog); 342 if (mOnCancelListener != null) { 343 mOnCancelListener.onCancel(dialog); 344 } 345 } 346 347 @Override 348 public void onDismiss(DialogInterface dialog) { 349 super.onDismiss(dialog); 350 if (mOnDismissListener != null) { 351 mOnDismissListener.onDismiss(dialog); 352 } 353 } 354 355 public int getDialogId() { 356 return mDialogId; 357 } 358 359 @Override 360 public void onDetach() { 361 super.onDetach(); 362 363 // This dialog fragment could be created from non-SettingsPreferenceFragment 364 if (mParentFragment instanceof SettingsPreferenceFragment) { 365 // in case the dialog is not explicitly removed by removeDialog() 366 if (((SettingsPreferenceFragment) mParentFragment).mDialogFragment == this) { 367 ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = null; 368 } 369 } 370 } 371 } 372 373 protected boolean hasNextButton() { 374 return ((ButtonBarHandler)getActivity()).hasNextButton(); 375 } 376 377 protected Button getNextButton() { 378 return ((ButtonBarHandler)getActivity()).getNextButton(); 379 } 380 381 public void finish() { 382 getActivity().onBackPressed(); 383 } 384 385 public boolean startFragment( 386 Fragment caller, String fragmentClass, int requestCode, Bundle extras) { 387 if (getActivity() instanceof SettingsActivity) { 388 SettingsActivity sa = (SettingsActivity) getActivity(); 389 sa.startPreferencePanel(fragmentClass, extras, 390 R.string.lock_settings_picker_title, null, caller, requestCode); 391 return true; 392 } else { 393 Log.w(TAG, "Parent isn't Settings activity, thus there's no way to launch the " 394 + "given Fragment (name: " + fragmentClass + ", requestCode: " + requestCode 395 + ")"); 396 return false; 397 } 398 } 399 400} 401