• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 package com.android.settings.dashboard;
17 
18 import android.app.Activity;
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.os.Bundle;
22 import android.support.annotation.VisibleForTesting;
23 import android.support.v7.preference.Preference;
24 import android.support.v7.preference.PreferenceManager;
25 import android.support.v7.preference.PreferenceScreen;
26 import android.text.TextUtils;
27 import android.util.ArrayMap;
28 import android.util.ArraySet;
29 import android.util.Log;
30 import android.view.LayoutInflater;
31 import android.view.View;
32 import android.view.ViewGroup;
33 
34 import com.android.settings.SettingsPreferenceFragment;
35 import com.android.settings.overlay.FeatureFactory;
36 import com.android.settings.search.Indexable;
37 import com.android.settingslib.core.AbstractPreferenceController;
38 import com.android.settingslib.drawer.DashboardCategory;
39 import com.android.settingslib.drawer.SettingsDrawerActivity;
40 import com.android.settingslib.drawer.Tile;
41 import com.android.settingslib.drawer.TileUtils;
42 
43 import java.util.ArrayList;
44 import java.util.Collection;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Set;
48 
49 /**
50  * Base fragment for dashboard style UI containing a list of static and dynamic setting items.
51  */
52 public abstract class DashboardFragment extends SettingsPreferenceFragment
53         implements SettingsDrawerActivity.CategoryListener, Indexable,
54         SummaryLoader.SummaryConsumer {
55     private static final String TAG = "DashboardFragment";
56 
57     private final Map<Class, AbstractPreferenceController> mPreferenceControllers =
58             new ArrayMap<>();
59     private final Set<String> mDashboardTilePrefKeys = new ArraySet<>();
60 
61     protected ProgressiveDisclosureMixin mProgressiveDisclosureMixin;
62     protected DashboardFeatureProvider mDashboardFeatureProvider;
63     private DashboardTilePlaceholderPreferenceController mPlaceholderPreferenceController;
64     private boolean mListeningToCategoryChange;
65     private SummaryLoader mSummaryLoader;
66 
67     @Override
onAttach(Context context)68     public void onAttach(Context context) {
69         super.onAttach(context);
70         mDashboardFeatureProvider =
71                 FeatureFactory.getFactory(context).getDashboardFeatureProvider(context);
72         mProgressiveDisclosureMixin = mDashboardFeatureProvider
73                 .getProgressiveDisclosureMixin(context, this, getArguments());
74         getLifecycle().addObserver(mProgressiveDisclosureMixin);
75 
76         List<AbstractPreferenceController> controllers = getPreferenceControllers(context);
77         if (controllers == null) {
78             controllers = new ArrayList<>();
79         }
80         mPlaceholderPreferenceController =
81                 new DashboardTilePlaceholderPreferenceController(context);
82         controllers.add(mPlaceholderPreferenceController);
83         for (AbstractPreferenceController controller : controllers) {
84             addPreferenceController(controller);
85         }
86     }
87 
88     @Override
onCreate(Bundle icicle)89     public void onCreate(Bundle icicle) {
90         super.onCreate(icicle);
91         // Set ComparisonCallback so we get better animation when list changes.
92         getPreferenceManager().setPreferenceComparisonCallback(
93                 new PreferenceManager.SimplePreferenceComparisonCallback());
94         // Upon rotation configuration change we need to update preference states before any
95         // editing dialog is recreated (that would happen before onResume is called).
96         updatePreferenceStates();
97     }
98 
99     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)100     public View onCreateView(LayoutInflater inflater, ViewGroup container,
101             Bundle savedInstanceState) {
102         final View view = super.onCreateView(inflater, container, savedInstanceState);
103         return view;
104     }
105 
106     @Override
onCategoriesChanged()107     public void onCategoriesChanged() {
108         final DashboardCategory category =
109                 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
110         if (category == null) {
111             return;
112         }
113         refreshDashboardTiles(getLogTag());
114     }
115 
116     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)117     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
118         super.onCreatePreferences(savedInstanceState, rootKey);
119         refreshAllPreferences(getLogTag());
120     }
121 
122     @Override
onStart()123     public void onStart() {
124         super.onStart();
125         final DashboardCategory category =
126                 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
127         if (category == null) {
128             return;
129         }
130         if (mSummaryLoader != null) {
131             // SummaryLoader can be null when there is no dynamic tiles.
132             mSummaryLoader.setListening(true);
133         }
134         final Activity activity = getActivity();
135         if (activity instanceof SettingsDrawerActivity) {
136             mListeningToCategoryChange = true;
137             ((SettingsDrawerActivity) activity).addCategoryListener(this);
138         }
139     }
140 
141     @Override
notifySummaryChanged(Tile tile)142     public void notifySummaryChanged(Tile tile) {
143         final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
144         final Preference pref = mProgressiveDisclosureMixin.findPreference(
145                 getPreferenceScreen(), key);
146         if (pref == null) {
147             Log.d(getLogTag(),
148                     String.format("Can't find pref by key %s, skipping update summary %s/%s",
149                             key, tile.title, tile.summary));
150             return;
151         }
152         pref.setSummary(tile.summary);
153     }
154 
155     @Override
onResume()156     public void onResume() {
157         super.onResume();
158         updatePreferenceStates();
159     }
160 
161     @Override
onPreferenceTreeClick(Preference preference)162     public boolean onPreferenceTreeClick(Preference preference) {
163         Collection<AbstractPreferenceController> controllers = mPreferenceControllers.values();
164         // If preference contains intent, log it before handling.
165         mMetricsFeatureProvider.logDashboardStartIntent(
166                 getContext(), preference.getIntent(), getMetricsCategory());
167         // Give all controllers a chance to handle click.
168         for (AbstractPreferenceController controller : controllers) {
169             if (controller.handlePreferenceTreeClick(preference)) {
170                 return true;
171             }
172         }
173         return super.onPreferenceTreeClick(preference);
174     }
175 
176     @Override
onStop()177     public void onStop() {
178         super.onStop();
179         if (mSummaryLoader != null) {
180             // SummaryLoader can be null when there is no dynamic tiles.
181             mSummaryLoader.setListening(false);
182         }
183         if (mListeningToCategoryChange) {
184             final Activity activity = getActivity();
185             if (activity instanceof SettingsDrawerActivity) {
186                 ((SettingsDrawerActivity) activity).remCategoryListener(this);
187             }
188             mListeningToCategoryChange = false;
189         }
190     }
191 
getPreferenceController(Class<T> clazz)192     protected <T extends AbstractPreferenceController> T getPreferenceController(Class<T> clazz) {
193         AbstractPreferenceController controller = mPreferenceControllers.get(clazz);
194         return (T) controller;
195     }
196 
addPreferenceController(AbstractPreferenceController controller)197     protected void addPreferenceController(AbstractPreferenceController controller) {
198         mPreferenceControllers.put(controller.getClass(), controller);
199     }
200 
201     /**
202      * Returns the CategoryKey for loading {@link DashboardCategory} for this fragment.
203      */
204     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
getCategoryKey()205     public String getCategoryKey() {
206         return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
207     }
208 
209     /**
210      * Get the tag string for logging.
211      */
getLogTag()212     protected abstract String getLogTag();
213 
214     /**
215      * Get the res id for static preference xml for this fragment.
216      */
getPreferenceScreenResId()217     protected abstract int getPreferenceScreenResId();
218 
219     /**
220      * Get a list of {@link AbstractPreferenceController} for this fragment.
221      */
getPreferenceControllers(Context context)222     protected abstract List<AbstractPreferenceController> getPreferenceControllers(Context context);
223 
224     /**
225      * Returns true if this tile should be displayed
226      */
displayTile(Tile tile)227     protected boolean displayTile(Tile tile) {
228         return true;
229     }
230 
231     @VisibleForTesting
tintTileIcon(Tile tile)232     boolean tintTileIcon(Tile tile) {
233         if (tile.icon == null) {
234             return false;
235         }
236         // First check if the tile has set the icon tintable metadata.
237         final Bundle metadata = tile.metaData;
238         if (metadata != null
239                 && metadata.containsKey(TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE)) {
240             return metadata.getBoolean(TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE);
241         }
242         final String pkgName = getContext().getPackageName();
243         // If this drawable is coming from outside Settings, tint it to match the color.
244         return pkgName != null && tile.intent != null
245                 && !pkgName.equals(tile.intent.getComponent().getPackageName());
246     }
247 
248     /**
249      * Displays resource based tiles.
250      */
displayResourceTiles()251     private void displayResourceTiles() {
252         final int resId = getPreferenceScreenResId();
253         if (resId <= 0) {
254             return;
255         }
256         addPreferencesFromResource(resId);
257         final PreferenceScreen screen = getPreferenceScreen();
258         Collection<AbstractPreferenceController> controllers = mPreferenceControllers.values();
259         for (AbstractPreferenceController controller : controllers) {
260             controller.displayPreference(screen);
261         }
262     }
263 
264     /**
265      * Update state of each preference managed by PreferenceController.
266      */
updatePreferenceStates()267     protected void updatePreferenceStates() {
268         Collection<AbstractPreferenceController> controllers = mPreferenceControllers.values();
269         final PreferenceScreen screen = getPreferenceScreen();
270         for (AbstractPreferenceController controller : controllers) {
271             if (!controller.isAvailable()) {
272                 continue;
273             }
274             final String key = controller.getPreferenceKey();
275 
276             final Preference preference = mProgressiveDisclosureMixin.findPreference(screen, key);
277             if (preference == null) {
278                 Log.d(TAG, String.format("Cannot find preference with key %s in Controller %s",
279                         key, controller.getClass().getSimpleName()));
280                 continue;
281             }
282             controller.updateState(preference);
283         }
284     }
285 
286     /**
287      * Refresh all preference items, including both static prefs from xml, and dynamic items from
288      * DashboardCategory.
289      */
refreshAllPreferences(final String TAG)290     private void refreshAllPreferences(final String TAG) {
291         // First remove old preferences.
292         if (getPreferenceScreen() != null) {
293             // Intentionally do not cache PreferenceScreen because it will be recreated later.
294             getPreferenceScreen().removeAll();
295         }
296 
297         // Add resource based tiles.
298         displayResourceTiles();
299         mProgressiveDisclosureMixin.collapse(getPreferenceScreen());
300 
301         refreshDashboardTiles(TAG);
302     }
303 
304     /**
305      * Refresh preference items backed by DashboardCategory.
306      */
307     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
refreshDashboardTiles(final String TAG)308     void refreshDashboardTiles(final String TAG) {
309         final PreferenceScreen screen = getPreferenceScreen();
310 
311         final DashboardCategory category =
312                 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
313         if (category == null) {
314             Log.d(TAG, "NO dashboard tiles for " + TAG);
315             return;
316         }
317         List<Tile> tiles = category.tiles;
318         if (tiles == null) {
319             Log.d(TAG, "tile list is empty, skipping category " + category.title);
320             return;
321         }
322         // Create a list to track which tiles are to be removed.
323         final List<String> remove = new ArrayList<>(mDashboardTilePrefKeys);
324 
325         // There are dashboard tiles, so we need to install SummaryLoader.
326         if (mSummaryLoader != null) {
327             mSummaryLoader.release();
328         }
329         final Context context = getContext();
330         mSummaryLoader = new SummaryLoader(getActivity(), getCategoryKey());
331         mSummaryLoader.setSummaryConsumer(this);
332         final TypedArray a = context.obtainStyledAttributes(new int[]{
333                 android.R.attr.colorControlNormal});
334         final int tintColor = a.getColor(0, context.getColor(android.R.color.white));
335         a.recycle();
336         // Install dashboard tiles.
337         for (Tile tile : tiles) {
338             final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
339             if (TextUtils.isEmpty(key)) {
340                 Log.d(TAG, "tile does not contain a key, skipping " + tile);
341                 continue;
342             }
343             if (!displayTile(tile)) {
344                 continue;
345             }
346             if (tintTileIcon(tile)) {
347                 tile.icon.setTint(tintColor);
348             }
349             if (mDashboardTilePrefKeys.contains(key)) {
350                 // Have the key already, will rebind.
351                 final Preference preference = mProgressiveDisclosureMixin.findPreference(
352                         screen, key);
353                 mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), getMetricsCategory(),
354                         preference, tile, key, mPlaceholderPreferenceController.getOrder());
355             } else {
356                 // Don't have this key, add it.
357                 final Preference pref = new Preference(getPrefContext());
358                 mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), getMetricsCategory(),
359                         pref, tile, key, mPlaceholderPreferenceController.getOrder());
360                 mProgressiveDisclosureMixin.addPreference(screen, pref);
361                 mDashboardTilePrefKeys.add(key);
362             }
363             remove.remove(key);
364         }
365         // Finally remove tiles that are gone.
366         for (String key : remove) {
367             mDashboardTilePrefKeys.remove(key);
368             mProgressiveDisclosureMixin.removePreference(screen, key);
369         }
370         mSummaryLoader.setListening(true);
371     }
372 }
373