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