OSDN Git Service

2fceb6399a00fee363c86eb25cc0bd37e2dc5780
[android-x86/packages-apps-Settings.git] / src / com / android / settings / SettingsPreferenceFragment.java
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.PreferenceScreen;
34 import android.support.v7.widget.LinearLayoutManager;
35 import android.support.v7.widget.RecyclerView;
36 import android.text.TextUtils;
37 import android.util.ArrayMap;
38 import android.util.Log;
39 import android.view.LayoutInflater;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.widget.Button;
43
44 import com.android.settings.applications.LayoutPreference;
45 import com.android.settings.core.InstrumentedPreferenceFragment;
46 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
47 import com.android.settings.search.actionbar.SearchMenuController;
48 import com.android.settings.support.actionbar.HelpMenuController;
49 import com.android.settings.support.actionbar.HelpResourceProvider;
50 import com.android.settings.widget.HighlightablePreferenceGroupAdapter;
51 import com.android.settings.widget.LoadingViewController;
52 import com.android.settingslib.CustomDialogPreference;
53 import com.android.settingslib.CustomEditTextPreference;
54 import com.android.settingslib.core.instrumentation.Instrumentable;
55 import com.android.settingslib.widget.FooterPreferenceMixin;
56
57 import java.util.UUID;
58
59 /**
60  * Base class for Settings fragments, with some helper functions and dialog management.
61  */
62 public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceFragment
63         implements DialogCreatable, HelpResourceProvider {
64
65     private static final String TAG = "SettingsPreference";
66
67     private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
68
69     protected final FooterPreferenceMixin mFooterPreferenceMixin =
70             new FooterPreferenceMixin(this, getLifecycle());
71
72
73     private static final int ORDER_FIRST = -1;
74
75     private SettingsDialogFragment mDialogFragment;
76     // Cache the content resolver for async callbacks
77     private ContentResolver mContentResolver;
78
79     private RecyclerView.Adapter mCurrentRootAdapter;
80     private boolean mIsDataSetObserverRegistered = false;
81     private RecyclerView.AdapterDataObserver mDataSetObserver =
82             new RecyclerView.AdapterDataObserver() {
83                 @Override
84                 public void onChanged() {
85                     onDataSetChanged();
86                 }
87
88                 @Override
89                 public void onItemRangeChanged(int positionStart, int itemCount) {
90                     onDataSetChanged();
91                 }
92
93                 @Override
94                 public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
95                     onDataSetChanged();
96                 }
97
98                 @Override
99                 public void onItemRangeInserted(int positionStart, int itemCount) {
100                     onDataSetChanged();
101                 }
102
103                 @Override
104                 public void onItemRangeRemoved(int positionStart, int itemCount) {
105                     onDataSetChanged();
106                 }
107
108                 @Override
109                 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
110                     onDataSetChanged();
111                 }
112             };
113
114     private ViewGroup mPinnedHeaderFrameLayout;
115     private ViewGroup mButtonBar;
116
117     private LayoutPreference mHeader;
118
119     private View mEmptyView;
120     private LinearLayoutManager mLayoutManager;
121     private ArrayMap<String, Preference> mPreferenceCache;
122     private boolean mAnimationAllowed;
123
124     @VisibleForTesting
125     public HighlightablePreferenceGroupAdapter mAdapter;
126     @VisibleForTesting
127     public boolean mPreferenceHighlighted = false;
128
129     @Override
130     public void onCreate(Bundle icicle) {
131         super.onCreate(icicle);
132         SearchMenuController.init(this /* host */);
133         HelpMenuController.init(this /* host */);
134
135         if (icicle != null) {
136             mPreferenceHighlighted = icicle.getBoolean(SAVE_HIGHLIGHTED_KEY);
137         }
138         final Bundle arguments = getArguments();
139
140         // Check if we should keep the preferences expanded.
141         if (arguments != null) {
142             final String highlightKey =
143                     arguments.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY);
144             if (!TextUtils.isEmpty(highlightKey)) {
145                 final PreferenceScreen screen = getPreferenceScreen();
146                 if (screen != null) {
147                     screen.setInitialExpandedChildrenCount(Integer.MAX_VALUE);
148                 }
149             }
150         }
151     }
152
153     @Override
154     public View onCreateView(LayoutInflater inflater, ViewGroup container,
155             Bundle savedInstanceState) {
156         final View root = super.onCreateView(inflater, container, savedInstanceState);
157         mPinnedHeaderFrameLayout = root.findViewById(R.id.pinned_header);
158         mButtonBar = root.findViewById(R.id.button_bar);
159         return root;
160     }
161
162     @Override
163     public void addPreferencesFromResource(@XmlRes int preferencesResId) {
164         super.addPreferencesFromResource(preferencesResId);
165         checkAvailablePrefs(getPreferenceScreen());
166     }
167
168     private void checkAvailablePrefs(PreferenceGroup preferenceGroup) {
169         if (preferenceGroup == null) return;
170         for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) {
171             Preference pref = preferenceGroup.getPreference(i);
172             if (pref instanceof SelfAvailablePreference
173                     && !((SelfAvailablePreference) pref).isAvailable(getContext())) {
174                 preferenceGroup.removePreference(pref);
175             } else if (pref instanceof PreferenceGroup) {
176                 checkAvailablePrefs((PreferenceGroup) pref);
177             }
178         }
179     }
180
181     public ViewGroup getButtonBar() {
182         return mButtonBar;
183     }
184
185     public View setPinnedHeaderView(int layoutResId) {
186         final LayoutInflater inflater = getActivity().getLayoutInflater();
187         final View pinnedHeader =
188                 inflater.inflate(layoutResId, mPinnedHeaderFrameLayout, false);
189         setPinnedHeaderView(pinnedHeader);
190         return pinnedHeader;
191     }
192
193     public void setPinnedHeaderView(View pinnedHeader) {
194         mPinnedHeaderFrameLayout.addView(pinnedHeader);
195         mPinnedHeaderFrameLayout.setVisibility(View.VISIBLE);
196     }
197
198     @Override
199     public void onSaveInstanceState(Bundle outState) {
200         super.onSaveInstanceState(outState);
201
202         if (mAdapter != null) {
203             outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mAdapter.isHighlightRequested());
204         }
205     }
206
207     @Override
208     public void onActivityCreated(Bundle savedInstanceState) {
209         super.onActivityCreated(savedInstanceState);
210         setHasOptionsMenu(true);
211     }
212
213     @Override
214     public void onResume() {
215         super.onResume();
216         highlightPreferenceIfNeeded();
217     }
218
219     @Override
220     protected void onBindPreferences() {
221         registerObserverIfNeeded();
222     }
223
224     @Override
225     protected void onUnbindPreferences() {
226         unregisterObserverIfNeeded();
227     }
228
229     public void setLoading(boolean loading, boolean animate) {
230         View loadingContainer = getView().findViewById(R.id.loading_container);
231         LoadingViewController.handleLoadingContainer(loadingContainer, getListView(),
232                 !loading /* done */,
233                 animate);
234     }
235
236     public void registerObserverIfNeeded() {
237         if (!mIsDataSetObserverRegistered) {
238             if (mCurrentRootAdapter != null) {
239                 mCurrentRootAdapter.unregisterAdapterDataObserver(mDataSetObserver);
240             }
241             mCurrentRootAdapter = getListView().getAdapter();
242             mCurrentRootAdapter.registerAdapterDataObserver(mDataSetObserver);
243             mIsDataSetObserverRegistered = true;
244             onDataSetChanged();
245         }
246     }
247
248     public void unregisterObserverIfNeeded() {
249         if (mIsDataSetObserverRegistered) {
250             if (mCurrentRootAdapter != null) {
251                 mCurrentRootAdapter.unregisterAdapterDataObserver(mDataSetObserver);
252                 mCurrentRootAdapter = null;
253             }
254             mIsDataSetObserverRegistered = false;
255         }
256     }
257
258     public void highlightPreferenceIfNeeded() {
259         if (!isAdded()) {
260             return;
261         }
262         if (mAdapter != null) {
263             mAdapter.requestHighlight(getView(), getListView());
264         }
265     }
266
267     protected void onDataSetChanged() {
268         highlightPreferenceIfNeeded();
269         updateEmptyView();
270     }
271
272     public LayoutPreference getHeaderView() {
273         return mHeader;
274     }
275
276     protected void setHeaderView(int resource) {
277         mHeader = new LayoutPreference(getPrefContext(), resource);
278         addPreferenceToTop(mHeader);
279     }
280
281     protected void setHeaderView(View view) {
282         mHeader = new LayoutPreference(getPrefContext(), view);
283         addPreferenceToTop(mHeader);
284     }
285
286     private void addPreferenceToTop(LayoutPreference preference) {
287         preference.setOrder(ORDER_FIRST);
288         if (getPreferenceScreen() != null) {
289             getPreferenceScreen().addPreference(preference);
290         }
291     }
292
293     @Override
294     public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
295         if (preferenceScreen != null && !preferenceScreen.isAttached()) {
296             // Without ids generated, the RecyclerView won't animate changes to the preferences.
297             preferenceScreen.setShouldUseGeneratedIds(mAnimationAllowed);
298         }
299         super.setPreferenceScreen(preferenceScreen);
300         if (preferenceScreen != null) {
301             if (mHeader != null) {
302                 preferenceScreen.addPreference(mHeader);
303             }
304         }
305     }
306
307     @VisibleForTesting
308     void updateEmptyView() {
309         if (mEmptyView == null) return;
310         if (getPreferenceScreen() != null) {
311             final View listContainer = getActivity().findViewById(android.R.id.list_container);
312             boolean show = (getPreferenceScreen().getPreferenceCount()
313                     - (mHeader != null ? 1 : 0)
314                     - (mFooterPreferenceMixin.hasFooter() ? 1 : 0)) <= 0
315                     || (listContainer != null && listContainer.getVisibility() != View.VISIBLE);
316             mEmptyView.setVisibility(show ? View.VISIBLE : View.GONE);
317         } else {
318             mEmptyView.setVisibility(View.VISIBLE);
319         }
320     }
321
322     public void setEmptyView(View v) {
323         if (mEmptyView != null) {
324             mEmptyView.setVisibility(View.GONE);
325         }
326         mEmptyView = v;
327         updateEmptyView();
328     }
329
330     public View getEmptyView() {
331         return mEmptyView;
332     }
333
334     @Override
335     public RecyclerView.LayoutManager onCreateLayoutManager() {
336         mLayoutManager = new LinearLayoutManager(getContext());
337         return mLayoutManager;
338     }
339
340     @Override
341     protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
342         final Bundle arguments = getArguments();
343         mAdapter = new HighlightablePreferenceGroupAdapter(preferenceScreen,
344                 arguments == null
345                         ? null : arguments.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY),
346                 mPreferenceHighlighted);
347         return mAdapter;
348     }
349
350     protected void setAnimationAllowed(boolean animationAllowed) {
351         mAnimationAllowed = animationAllowed;
352     }
353
354     protected void cacheRemoveAllPrefs(PreferenceGroup group) {
355         mPreferenceCache = new ArrayMap<>();
356         final int N = group.getPreferenceCount();
357         for (int i = 0; i < N; i++) {
358             Preference p = group.getPreference(i);
359             if (TextUtils.isEmpty(p.getKey())) {
360                 continue;
361             }
362             mPreferenceCache.put(p.getKey(), p);
363         }
364     }
365
366     protected Preference getCachedPreference(String key) {
367         return mPreferenceCache != null ? mPreferenceCache.remove(key) : null;
368     }
369
370     protected void removeCachedPrefs(PreferenceGroup group) {
371         for (Preference p : mPreferenceCache.values()) {
372             group.removePreference(p);
373         }
374         mPreferenceCache = null;
375     }
376
377     protected int getCachedCount() {
378         return mPreferenceCache != null ? mPreferenceCache.size() : 0;
379     }
380
381     protected boolean removePreference(String key) {
382         return removePreference(getPreferenceScreen(), key);
383     }
384
385     @VisibleForTesting
386     boolean removePreference(PreferenceGroup group, String key) {
387         final int preferenceCount = group.getPreferenceCount();
388         for (int i = 0; i < preferenceCount; i++) {
389             final Preference preference = group.getPreference(i);
390             final String curKey = preference.getKey();
391
392             if (TextUtils.equals(curKey, key)) {
393                 return group.removePreference(preference);
394             }
395
396             if (preference instanceof PreferenceGroup) {
397                 if (removePreference((PreferenceGroup) preference, key)) {
398                     return true;
399                 }
400             }
401         }
402         return false;
403     }
404
405     /*
406      * The name is intentionally made different from Activity#finish(), so that
407      * users won't misunderstand its meaning.
408      */
409     public final void finishFragment() {
410         getActivity().onBackPressed();
411     }
412
413     // Some helpers for functions used by the settings fragments when they were activities
414
415     /**
416      * Returns the ContentResolver from the owning Activity.
417      */
418     protected ContentResolver getContentResolver() {
419         Context context = getActivity();
420         if (context != null) {
421             mContentResolver = context.getContentResolver();
422         }
423         return mContentResolver;
424     }
425
426     /**
427      * Returns the specified system service from the owning Activity.
428      */
429     protected Object getSystemService(final String name) {
430         return getActivity().getSystemService(name);
431     }
432
433     /**
434      * Returns the PackageManager from the owning Activity.
435      */
436     protected PackageManager getPackageManager() {
437         return getActivity().getPackageManager();
438     }
439
440     @Override
441     public void onDetach() {
442         if (isRemoving()) {
443             if (mDialogFragment != null) {
444                 mDialogFragment.dismiss();
445                 mDialogFragment = null;
446             }
447         }
448         super.onDetach();
449     }
450
451     // Dialog management
452
453     protected void showDialog(int dialogId) {
454         if (mDialogFragment != null) {
455             Log.e(TAG, "Old dialog fragment not null!");
456         }
457         mDialogFragment = new SettingsDialogFragment(this, dialogId);
458         mDialogFragment.show(getChildFragmentManager(), Integer.toString(dialogId));
459     }
460
461     @Override
462     public Dialog onCreateDialog(int dialogId) {
463         return null;
464     }
465
466     @Override
467     public int getDialogMetricsCategory(int dialogId) {
468         return 0;
469     }
470
471     protected void removeDialog(int dialogId) {
472         // mDialogFragment may not be visible yet in parent fragment's onResume().
473         // To be able to dismiss dialog at that time, don't check
474         // mDialogFragment.isVisible().
475         if (mDialogFragment != null && mDialogFragment.getDialogId() == dialogId) {
476             mDialogFragment.dismissAllowingStateLoss();
477         }
478         mDialogFragment = null;
479     }
480
481     /**
482      * Sets the OnCancelListener of the dialog shown. This method can only be
483      * called after showDialog(int) and before removeDialog(int). The method
484      * does nothing otherwise.
485      */
486     protected void setOnCancelListener(DialogInterface.OnCancelListener listener) {
487         if (mDialogFragment != null) {
488             mDialogFragment.mOnCancelListener = listener;
489         }
490     }
491
492     /**
493      * Sets the OnDismissListener of the dialog shown. This method can only be
494      * called after showDialog(int) and before removeDialog(int). The method
495      * does nothing otherwise.
496      */
497     protected void setOnDismissListener(DialogInterface.OnDismissListener listener) {
498         if (mDialogFragment != null) {
499             mDialogFragment.mOnDismissListener = listener;
500         }
501     }
502
503     public void onDialogShowing() {
504         // override in subclass to attach a dismiss listener, for instance
505     }
506
507     @Override
508     public void onDisplayPreferenceDialog(Preference preference) {
509         if (preference.getKey() == null) {
510             // Auto-key preferences that don't have a key, so the dialog can find them.
511             preference.setKey(UUID.randomUUID().toString());
512         }
513         DialogFragment f = null;
514         if (preference instanceof RestrictedListPreference) {
515             f = RestrictedListPreference.RestrictedListPreferenceDialogFragment
516                     .newInstance(preference.getKey());
517         } else if (preference instanceof CustomListPreference) {
518             f = CustomListPreference.CustomListPreferenceDialogFragment
519                     .newInstance(preference.getKey());
520         } else if (preference instanceof CustomDialogPreference) {
521             f = CustomDialogPreference.CustomPreferenceDialogFragment
522                     .newInstance(preference.getKey());
523         } else if (preference instanceof CustomEditTextPreference) {
524             f = CustomEditTextPreference.CustomPreferenceDialogFragment
525                     .newInstance(preference.getKey());
526         } else {
527             super.onDisplayPreferenceDialog(preference);
528             return;
529         }
530         f.setTargetFragment(this, 0);
531         f.show(getFragmentManager(), "dialog_preference");
532         onDialogShowing();
533     }
534
535     public static class SettingsDialogFragment extends InstrumentedDialogFragment {
536         private static final String KEY_DIALOG_ID = "key_dialog_id";
537         private static final String KEY_PARENT_FRAGMENT_ID = "key_parent_fragment_id";
538
539         private Fragment mParentFragment;
540
541         private DialogInterface.OnCancelListener mOnCancelListener;
542         private DialogInterface.OnDismissListener mOnDismissListener;
543
544         public SettingsDialogFragment() {
545             /* do nothing */
546         }
547
548         public SettingsDialogFragment(DialogCreatable fragment, int dialogId) {
549             super(fragment, dialogId);
550             if (!(fragment instanceof Fragment)) {
551                 throw new IllegalArgumentException("fragment argument must be an instance of "
552                         + Fragment.class.getName());
553             }
554             mParentFragment = (Fragment) fragment;
555         }
556
557
558         @Override
559         public int getMetricsCategory() {
560             if (mDialogCreatable == null) {
561                 return Instrumentable.METRICS_CATEGORY_UNKNOWN;
562             }
563             final int metricsCategory = mDialogCreatable.getDialogMetricsCategory(mDialogId);
564             if (metricsCategory <= 0) {
565                 throw new IllegalStateException("Dialog must provide a metrics category");
566             }
567             return metricsCategory;
568         }
569
570         @Override
571         public void onSaveInstanceState(Bundle outState) {
572             super.onSaveInstanceState(outState);
573             if (mParentFragment != null) {
574                 outState.putInt(KEY_DIALOG_ID, mDialogId);
575                 outState.putInt(KEY_PARENT_FRAGMENT_ID, mParentFragment.getId());
576             }
577         }
578
579         @Override
580         public void onStart() {
581             super.onStart();
582
583             if (mParentFragment != null && mParentFragment instanceof SettingsPreferenceFragment) {
584                 ((SettingsPreferenceFragment) mParentFragment).onDialogShowing();
585             }
586         }
587
588         @Override
589         public Dialog onCreateDialog(Bundle savedInstanceState) {
590             if (savedInstanceState != null) {
591                 mDialogId = savedInstanceState.getInt(KEY_DIALOG_ID, 0);
592                 mParentFragment = getParentFragment();
593                 int mParentFragmentId = savedInstanceState.getInt(KEY_PARENT_FRAGMENT_ID, -1);
594                 if (mParentFragment == null) {
595                     mParentFragment = getFragmentManager().findFragmentById(mParentFragmentId);
596                 }
597                 if (!(mParentFragment instanceof DialogCreatable)) {
598                     throw new IllegalArgumentException(
599                             (mParentFragment != null
600                                     ? mParentFragment.getClass().getName()
601                                     : mParentFragmentId)
602                                     + " must implement "
603                                     + DialogCreatable.class.getName());
604                 }
605                 // This dialog fragment could be created from non-SettingsPreferenceFragment
606                 if (mParentFragment instanceof SettingsPreferenceFragment) {
607                     // restore mDialogFragment in mParentFragment
608                     ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = this;
609                 }
610             }
611             return ((DialogCreatable) mParentFragment).onCreateDialog(mDialogId);
612         }
613
614         @Override
615         public void onCancel(DialogInterface dialog) {
616             super.onCancel(dialog);
617             if (mOnCancelListener != null) {
618                 mOnCancelListener.onCancel(dialog);
619             }
620         }
621
622         @Override
623         public void onDismiss(DialogInterface dialog) {
624             super.onDismiss(dialog);
625             if (mOnDismissListener != null) {
626                 mOnDismissListener.onDismiss(dialog);
627             }
628         }
629
630         public int getDialogId() {
631             return mDialogId;
632         }
633
634         @Override
635         public void onDetach() {
636             super.onDetach();
637
638             // This dialog fragment could be created from non-SettingsPreferenceFragment
639             if (mParentFragment instanceof SettingsPreferenceFragment) {
640                 // in case the dialog is not explicitly removed by removeDialog()
641                 if (((SettingsPreferenceFragment) mParentFragment).mDialogFragment == this) {
642                     ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = null;
643                 }
644             }
645         }
646     }
647
648     protected boolean hasNextButton() {
649         return ((ButtonBarHandler) getActivity()).hasNextButton();
650     }
651
652     protected Button getNextButton() {
653         return ((ButtonBarHandler) getActivity()).getNextButton();
654     }
655
656     public void finish() {
657         Activity activity = getActivity();
658         if (activity == null) return;
659         if (getFragmentManager().getBackStackEntryCount() > 0) {
660             getFragmentManager().popBackStack();
661         } else {
662             activity.finish();
663         }
664     }
665
666     protected Intent getIntent() {
667         if (getActivity() == null) {
668             return null;
669         }
670         return getActivity().getIntent();
671     }
672
673     protected void setResult(int result, Intent intent) {
674         if (getActivity() == null) {
675             return;
676         }
677         getActivity().setResult(result, intent);
678     }
679
680     protected void setResult(int result) {
681         if (getActivity() == null) {
682             return;
683         }
684         getActivity().setResult(result);
685     }
686
687     public boolean startFragment(Fragment caller, String fragmentClass, int titleRes,
688             int requestCode, Bundle extras) {
689         final Activity activity = getActivity();
690         if (activity instanceof SettingsActivity) {
691             SettingsActivity sa = (SettingsActivity) activity;
692             sa.startPreferencePanel(
693                     caller, fragmentClass, extras, titleRes, null, caller, requestCode);
694             return true;
695         } else {
696             Log.w(TAG,
697                     "Parent isn't SettingsActivity nor PreferenceActivity, thus there's no way to "
698                             + "launch the given Fragment (name: " + fragmentClass
699                             + ", requestCode: " + requestCode + ")");
700             return false;
701         }
702     }
703
704 }