OSDN Git Service

Merge "Zen Condition text and primary click changes"
[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         mAdapter = new HighlightablePreferenceGroupAdapter(preferenceScreen,
343                 getArguments().getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY),
344                 mPreferenceHighlighted);
345         return mAdapter;
346     }
347
348     protected void setAnimationAllowed(boolean animationAllowed) {
349         mAnimationAllowed = animationAllowed;
350     }
351
352     protected void cacheRemoveAllPrefs(PreferenceGroup group) {
353         mPreferenceCache = new ArrayMap<>();
354         final int N = group.getPreferenceCount();
355         for (int i = 0; i < N; i++) {
356             Preference p = group.getPreference(i);
357             if (TextUtils.isEmpty(p.getKey())) {
358                 continue;
359             }
360             mPreferenceCache.put(p.getKey(), p);
361         }
362     }
363
364     protected Preference getCachedPreference(String key) {
365         return mPreferenceCache != null ? mPreferenceCache.remove(key) : null;
366     }
367
368     protected void removeCachedPrefs(PreferenceGroup group) {
369         for (Preference p : mPreferenceCache.values()) {
370             group.removePreference(p);
371         }
372         mPreferenceCache = null;
373     }
374
375     protected int getCachedCount() {
376         return mPreferenceCache != null ? mPreferenceCache.size() : 0;
377     }
378
379     protected boolean removePreference(String key) {
380         return removePreference(getPreferenceScreen(), key);
381     }
382
383     @VisibleForTesting
384     boolean removePreference(PreferenceGroup group, String key) {
385         final int preferenceCount = group.getPreferenceCount();
386         for (int i = 0; i < preferenceCount; i++) {
387             final Preference preference = group.getPreference(i);
388             final String curKey = preference.getKey();
389
390             if (TextUtils.equals(curKey, key)) {
391                 return group.removePreference(preference);
392             }
393
394             if (preference instanceof PreferenceGroup) {
395                 if (removePreference((PreferenceGroup) preference, key)) {
396                     return true;
397                 }
398             }
399         }
400         return false;
401     }
402
403     /*
404      * The name is intentionally made different from Activity#finish(), so that
405      * users won't misunderstand its meaning.
406      */
407     public final void finishFragment() {
408         getActivity().onBackPressed();
409     }
410
411     // Some helpers for functions used by the settings fragments when they were activities
412
413     /**
414      * Returns the ContentResolver from the owning Activity.
415      */
416     protected ContentResolver getContentResolver() {
417         Context context = getActivity();
418         if (context != null) {
419             mContentResolver = context.getContentResolver();
420         }
421         return mContentResolver;
422     }
423
424     /**
425      * Returns the specified system service from the owning Activity.
426      */
427     protected Object getSystemService(final String name) {
428         return getActivity().getSystemService(name);
429     }
430
431     /**
432      * Returns the PackageManager from the owning Activity.
433      */
434     protected PackageManager getPackageManager() {
435         return getActivity().getPackageManager();
436     }
437
438     @Override
439     public void onDetach() {
440         if (isRemoving()) {
441             if (mDialogFragment != null) {
442                 mDialogFragment.dismiss();
443                 mDialogFragment = null;
444             }
445         }
446         super.onDetach();
447     }
448
449     // Dialog management
450
451     protected void showDialog(int dialogId) {
452         if (mDialogFragment != null) {
453             Log.e(TAG, "Old dialog fragment not null!");
454         }
455         mDialogFragment = new SettingsDialogFragment(this, dialogId);
456         mDialogFragment.show(getChildFragmentManager(), Integer.toString(dialogId));
457     }
458
459     @Override
460     public Dialog onCreateDialog(int dialogId) {
461         return null;
462     }
463
464     @Override
465     public int getDialogMetricsCategory(int dialogId) {
466         return 0;
467     }
468
469     protected void removeDialog(int dialogId) {
470         // mDialogFragment may not be visible yet in parent fragment's onResume().
471         // To be able to dismiss dialog at that time, don't check
472         // mDialogFragment.isVisible().
473         if (mDialogFragment != null && mDialogFragment.getDialogId() == dialogId) {
474             mDialogFragment.dismissAllowingStateLoss();
475         }
476         mDialogFragment = null;
477     }
478
479     /**
480      * Sets the OnCancelListener of the dialog shown. This method can only be
481      * called after showDialog(int) and before removeDialog(int). The method
482      * does nothing otherwise.
483      */
484     protected void setOnCancelListener(DialogInterface.OnCancelListener listener) {
485         if (mDialogFragment != null) {
486             mDialogFragment.mOnCancelListener = listener;
487         }
488     }
489
490     /**
491      * Sets the OnDismissListener of the dialog shown. This method can only be
492      * called after showDialog(int) and before removeDialog(int). The method
493      * does nothing otherwise.
494      */
495     protected void setOnDismissListener(DialogInterface.OnDismissListener listener) {
496         if (mDialogFragment != null) {
497             mDialogFragment.mOnDismissListener = listener;
498         }
499     }
500
501     public void onDialogShowing() {
502         // override in subclass to attach a dismiss listener, for instance
503     }
504
505     @Override
506     public void onDisplayPreferenceDialog(Preference preference) {
507         if (preference.getKey() == null) {
508             // Auto-key preferences that don't have a key, so the dialog can find them.
509             preference.setKey(UUID.randomUUID().toString());
510         }
511         DialogFragment f = null;
512         if (preference instanceof RestrictedListPreference) {
513             f = RestrictedListPreference.RestrictedListPreferenceDialogFragment
514                     .newInstance(preference.getKey());
515         } else if (preference instanceof CustomListPreference) {
516             f = CustomListPreference.CustomListPreferenceDialogFragment
517                     .newInstance(preference.getKey());
518         } else if (preference instanceof CustomDialogPreference) {
519             f = CustomDialogPreference.CustomPreferenceDialogFragment
520                     .newInstance(preference.getKey());
521         } else if (preference instanceof CustomEditTextPreference) {
522             f = CustomEditTextPreference.CustomPreferenceDialogFragment
523                     .newInstance(preference.getKey());
524         } else {
525             super.onDisplayPreferenceDialog(preference);
526             return;
527         }
528         f.setTargetFragment(this, 0);
529         f.show(getFragmentManager(), "dialog_preference");
530         onDialogShowing();
531     }
532
533     public static class SettingsDialogFragment extends InstrumentedDialogFragment {
534         private static final String KEY_DIALOG_ID = "key_dialog_id";
535         private static final String KEY_PARENT_FRAGMENT_ID = "key_parent_fragment_id";
536
537         private Fragment mParentFragment;
538
539         private DialogInterface.OnCancelListener mOnCancelListener;
540         private DialogInterface.OnDismissListener mOnDismissListener;
541
542         public SettingsDialogFragment() {
543             /* do nothing */
544         }
545
546         public SettingsDialogFragment(DialogCreatable fragment, int dialogId) {
547             super(fragment, dialogId);
548             if (!(fragment instanceof Fragment)) {
549                 throw new IllegalArgumentException("fragment argument must be an instance of "
550                         + Fragment.class.getName());
551             }
552             mParentFragment = (Fragment) fragment;
553         }
554
555
556         @Override
557         public int getMetricsCategory() {
558             if (mDialogCreatable == null) {
559                 return Instrumentable.METRICS_CATEGORY_UNKNOWN;
560             }
561             final int metricsCategory = mDialogCreatable.getDialogMetricsCategory(mDialogId);
562             if (metricsCategory <= 0) {
563                 throw new IllegalStateException("Dialog must provide a metrics category");
564             }
565             return metricsCategory;
566         }
567
568         @Override
569         public void onSaveInstanceState(Bundle outState) {
570             super.onSaveInstanceState(outState);
571             if (mParentFragment != null) {
572                 outState.putInt(KEY_DIALOG_ID, mDialogId);
573                 outState.putInt(KEY_PARENT_FRAGMENT_ID, mParentFragment.getId());
574             }
575         }
576
577         @Override
578         public void onStart() {
579             super.onStart();
580
581             if (mParentFragment != null && mParentFragment instanceof SettingsPreferenceFragment) {
582                 ((SettingsPreferenceFragment) mParentFragment).onDialogShowing();
583             }
584         }
585
586         @Override
587         public Dialog onCreateDialog(Bundle savedInstanceState) {
588             if (savedInstanceState != null) {
589                 mDialogId = savedInstanceState.getInt(KEY_DIALOG_ID, 0);
590                 mParentFragment = getParentFragment();
591                 int mParentFragmentId = savedInstanceState.getInt(KEY_PARENT_FRAGMENT_ID, -1);
592                 if (mParentFragment == null) {
593                     mParentFragment = getFragmentManager().findFragmentById(mParentFragmentId);
594                 }
595                 if (!(mParentFragment instanceof DialogCreatable)) {
596                     throw new IllegalArgumentException(
597                             (mParentFragment != null
598                                     ? mParentFragment.getClass().getName()
599                                     : mParentFragmentId)
600                                     + " must implement "
601                                     + DialogCreatable.class.getName());
602                 }
603                 // This dialog fragment could be created from non-SettingsPreferenceFragment
604                 if (mParentFragment instanceof SettingsPreferenceFragment) {
605                     // restore mDialogFragment in mParentFragment
606                     ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = this;
607                 }
608             }
609             return ((DialogCreatable) mParentFragment).onCreateDialog(mDialogId);
610         }
611
612         @Override
613         public void onCancel(DialogInterface dialog) {
614             super.onCancel(dialog);
615             if (mOnCancelListener != null) {
616                 mOnCancelListener.onCancel(dialog);
617             }
618         }
619
620         @Override
621         public void onDismiss(DialogInterface dialog) {
622             super.onDismiss(dialog);
623             if (mOnDismissListener != null) {
624                 mOnDismissListener.onDismiss(dialog);
625             }
626         }
627
628         public int getDialogId() {
629             return mDialogId;
630         }
631
632         @Override
633         public void onDetach() {
634             super.onDetach();
635
636             // This dialog fragment could be created from non-SettingsPreferenceFragment
637             if (mParentFragment instanceof SettingsPreferenceFragment) {
638                 // in case the dialog is not explicitly removed by removeDialog()
639                 if (((SettingsPreferenceFragment) mParentFragment).mDialogFragment == this) {
640                     ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = null;
641                 }
642             }
643         }
644     }
645
646     protected boolean hasNextButton() {
647         return ((ButtonBarHandler) getActivity()).hasNextButton();
648     }
649
650     protected Button getNextButton() {
651         return ((ButtonBarHandler) getActivity()).getNextButton();
652     }
653
654     public void finish() {
655         Activity activity = getActivity();
656         if (activity == null) return;
657         if (getFragmentManager().getBackStackEntryCount() > 0) {
658             getFragmentManager().popBackStack();
659         } else {
660             activity.finish();
661         }
662     }
663
664     protected Intent getIntent() {
665         if (getActivity() == null) {
666             return null;
667         }
668         return getActivity().getIntent();
669     }
670
671     protected void setResult(int result, Intent intent) {
672         if (getActivity() == null) {
673             return;
674         }
675         getActivity().setResult(result, intent);
676     }
677
678     protected void setResult(int result) {
679         if (getActivity() == null) {
680             return;
681         }
682         getActivity().setResult(result);
683     }
684
685     public boolean startFragment(Fragment caller, String fragmentClass, int titleRes,
686             int requestCode, Bundle extras) {
687         final Activity activity = getActivity();
688         if (activity instanceof SettingsActivity) {
689             SettingsActivity sa = (SettingsActivity) activity;
690             sa.startPreferencePanel(
691                     caller, fragmentClass, extras, titleRes, null, caller, requestCode);
692             return true;
693         } else {
694             Log.w(TAG,
695                     "Parent isn't SettingsActivity nor PreferenceActivity, thus there's no way to "
696                             + "launch the given Fragment (name: " + fragmentClass
697                             + ", requestCode: " + requestCode + ")");
698             return false;
699         }
700     }
701
702 }