• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.dashboard;
18 
19 import android.app.Activity;
20 import android.content.Context;
21 import android.os.AsyncTask;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.support.annotation.VisibleForTesting;
25 import android.support.v7.widget.LinearLayoutManager;
26 import android.util.Log;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 
31 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
32 import com.android.settings.R;
33 import com.android.settings.core.InstrumentedFragment;
34 import com.android.settings.dashboard.conditional.Condition;
35 import com.android.settings.dashboard.conditional.ConditionManager;
36 import com.android.settings.dashboard.conditional.ConditionManager.ConditionListener;
37 import com.android.settings.dashboard.conditional.FocusRecyclerView;
38 import com.android.settings.dashboard.conditional.FocusRecyclerView.FocusListener;
39 import com.android.settings.dashboard.suggestions.SuggestionDismissController;
40 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
41 import com.android.settings.dashboard.suggestions.SuggestionsChecks;
42 import com.android.settings.overlay.FeatureFactory;
43 import com.android.settings.widget.ActionBarShadowController;
44 import com.android.settingslib.drawer.CategoryKey;
45 import com.android.settingslib.drawer.DashboardCategory;
46 import com.android.settingslib.drawer.SettingsDrawerActivity;
47 import com.android.settingslib.drawer.SettingsDrawerActivity.CategoryListener;
48 import com.android.settingslib.drawer.Tile;
49 import com.android.settingslib.suggestions.SuggestionList;
50 import com.android.settingslib.suggestions.SuggestionParser;
51 
52 import java.util.ArrayList;
53 import java.util.List;
54 
55 public class DashboardSummary extends InstrumentedFragment
56         implements CategoryListener, ConditionListener,
57         FocusListener, SuggestionDismissController.Callback {
58     public static final boolean DEBUG = false;
59     private static final boolean DEBUG_TIMING = false;
60     private static final int MAX_WAIT_MILLIS = 700;
61     private static final String TAG = "DashboardSummary";
62 
63 
64     private static final String EXTRA_SCROLL_POSITION = "scroll_position";
65 
66     private final Handler mHandler = new Handler();
67 
68     private FocusRecyclerView mDashboard;
69     private DashboardAdapter mAdapter;
70     private SummaryLoader mSummaryLoader;
71     private ConditionManager mConditionManager;
72     private SuggestionParser mSuggestionParser;
73     private LinearLayoutManager mLayoutManager;
74     private SuggestionsChecks mSuggestionsChecks;
75     private DashboardFeatureProvider mDashboardFeatureProvider;
76     private SuggestionFeatureProvider mSuggestionFeatureProvider;
77     private boolean isOnCategoriesChangedCalled;
78     private boolean mOnConditionsChangedCalled;
79 
80     @Override
getMetricsCategory()81     public int getMetricsCategory() {
82         return MetricsEvent.DASHBOARD_SUMMARY;
83     }
84 
85     @Override
onCreate(Bundle savedInstanceState)86     public void onCreate(Bundle savedInstanceState) {
87         long startTime = System.currentTimeMillis();
88         super.onCreate(savedInstanceState);
89         final Activity activity = getActivity();
90         mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
91                 .getDashboardFeatureProvider(activity);
92         mSuggestionFeatureProvider = FeatureFactory.getFactory(activity)
93                 .getSuggestionFeatureProvider(activity);
94 
95         mSummaryLoader = new SummaryLoader(activity, CategoryKey.CATEGORY_HOMEPAGE);
96 
97         mConditionManager = ConditionManager.get(activity, false);
98         getLifecycle().addObserver(mConditionManager);
99         if (mSuggestionFeatureProvider.isSuggestionEnabled(activity)) {
100             mSuggestionParser = new SuggestionParser(activity,
101                     mSuggestionFeatureProvider.getSharedPrefs(activity), R.xml.suggestion_ordering);
102             mSuggestionsChecks = new SuggestionsChecks(getContext());
103         }
104         if (DEBUG_TIMING) {
105             Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime)
106                     + " ms");
107         }
108     }
109 
110     @Override
onDestroy()111     public void onDestroy() {
112         mSummaryLoader.release();
113         super.onDestroy();
114     }
115 
116     @Override
onResume()117     public void onResume() {
118         long startTime = System.currentTimeMillis();
119         super.onResume();
120 
121         ((SettingsDrawerActivity) getActivity()).addCategoryListener(this);
122         mSummaryLoader.setListening(true);
123         final int metricsCategory = getMetricsCategory();
124         for (Condition c : mConditionManager.getConditions()) {
125             if (c.shouldShow()) {
126                 mMetricsFeatureProvider.visible(getContext(), metricsCategory,
127                         c.getMetricsConstant());
128             }
129         }
130         if (DEBUG_TIMING) {
131             Log.d(TAG, "onResume took " + (System.currentTimeMillis() - startTime) + " ms");
132         }
133     }
134 
135     @Override
onPause()136     public void onPause() {
137         super.onPause();
138 
139         ((SettingsDrawerActivity) getActivity()).remCategoryListener(this);
140         mSummaryLoader.setListening(false);
141         for (Condition c : mConditionManager.getConditions()) {
142             if (c.shouldShow()) {
143                 mMetricsFeatureProvider.hidden(getContext(), c.getMetricsConstant());
144             }
145         }
146         if (!getActivity().isChangingConfigurations()) {
147             mAdapter.onPause();
148         }
149     }
150 
151     @Override
onWindowFocusChanged(boolean hasWindowFocus)152     public void onWindowFocusChanged(boolean hasWindowFocus) {
153         long startTime = System.currentTimeMillis();
154         if (hasWindowFocus) {
155             Log.d(TAG, "Listening for condition changes");
156             mConditionManager.addListener(this);
157             Log.d(TAG, "conditions refreshed");
158             mConditionManager.refreshAll();
159         } else {
160             Log.d(TAG, "Stopped listening for condition changes");
161             mConditionManager.remListener(this);
162         }
163         if (DEBUG_TIMING) {
164             Log.d(TAG, "onWindowFocusChanged took "
165                     + (System.currentTimeMillis() - startTime) + " ms");
166         }
167     }
168 
169     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)170     public View onCreateView(LayoutInflater inflater, ViewGroup container,
171             Bundle savedInstanceState) {
172         return inflater.inflate(R.layout.dashboard, container, false);
173     }
174 
175     @Override
onSaveInstanceState(Bundle outState)176     public void onSaveInstanceState(Bundle outState) {
177         super.onSaveInstanceState(outState);
178         if (mLayoutManager == null) return;
179         outState.putInt(EXTRA_SCROLL_POSITION, mLayoutManager.findFirstVisibleItemPosition());
180         if (mAdapter != null) {
181             mAdapter.onSaveInstanceState(outState);
182         }
183     }
184 
185     @Override
onViewCreated(View view, Bundle bundle)186     public void onViewCreated(View view, Bundle bundle) {
187         long startTime = System.currentTimeMillis();
188         mDashboard = view.findViewById(R.id.dashboard_container);
189         mLayoutManager = new LinearLayoutManager(getContext());
190         mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
191         if (bundle != null) {
192             int scrollPosition = bundle.getInt(EXTRA_SCROLL_POSITION);
193             mLayoutManager.scrollToPosition(scrollPosition);
194         }
195         mDashboard.setLayoutManager(mLayoutManager);
196         mDashboard.setHasFixedSize(true);
197         mDashboard.setListener(this);
198         mAdapter = new DashboardAdapter(getContext(), bundle, mConditionManager.getConditions(),
199             mSuggestionParser, this /* SuggestionDismissController.Callback */);
200         mDashboard.setAdapter(mAdapter);
201         mDashboard.setItemAnimator(new DashboardItemAnimator());
202         mSummaryLoader.setSummaryConsumer(mAdapter);
203         ActionBarShadowController.attachToRecyclerView(
204                 getActivity().findViewById(R.id.search_bar_container), getLifecycle(), mDashboard);
205 
206         if (DEBUG_TIMING) {
207             Log.d(TAG, "onViewCreated took "
208                     + (System.currentTimeMillis() - startTime) + " ms");
209         }
210         rebuildUI();
211     }
212 
213     @VisibleForTesting
rebuildUI()214     void rebuildUI() {
215         if (!mSuggestionFeatureProvider.isSuggestionEnabled(getContext())) {
216             Log.d(TAG, "Suggestion feature is disabled, skipping suggestion entirely");
217             updateCategoryAndSuggestion(null /* tiles */);
218         } else {
219             new SuggestionLoader().execute();
220             // Set categories on their own if loading suggestions takes too long.
221             mHandler.postDelayed(() -> {
222                 updateCategoryAndSuggestion(null /* tiles */);
223             }, MAX_WAIT_MILLIS);
224         }
225     }
226 
227     @Override
onCategoriesChanged()228     public void onCategoriesChanged() {
229         // Bypass rebuildUI() on the first call of onCategoriesChanged, since rebuildUI() happens
230         // in onViewCreated as well when app starts. But, on the subsequent calls we need to
231         // rebuildUI() because there might be some changes to suggestions and categories.
232         if (isOnCategoriesChangedCalled) {
233             rebuildUI();
234         }
235         isOnCategoriesChangedCalled = true;
236     }
237 
238     @Override
onConditionsChanged()239     public void onConditionsChanged() {
240         Log.d(TAG, "onConditionsChanged");
241         // Bypass refreshing the conditions on the first call of onConditionsChanged.
242         // onConditionsChanged is called immediately everytime we start listening to the conditions
243         // change when we gain window focus. Since the conditions are passed to the adapter's
244         // constructor when we create the view, the first handling is not necessary.
245         // But, on the subsequent calls we need to handle it because there might be real changes to
246         // conditions.
247         if (mOnConditionsChangedCalled) {
248             final boolean scrollToTop =
249                     mLayoutManager.findFirstCompletelyVisibleItemPosition() <= 1;
250             mAdapter.setConditions(mConditionManager.getConditions());
251             if (scrollToTop) {
252                 mDashboard.scrollToPosition(0);
253             }
254         } else {
255             mOnConditionsChangedCalled = true;
256         }
257     }
258 
259     @Override
getSuggestionForPosition(int position)260     public Tile getSuggestionForPosition(int position) {
261         return mAdapter.getSuggestion(position);
262     }
263 
264     @Override
onSuggestionDismissed(Tile suggestion)265     public void onSuggestionDismissed(Tile suggestion) {
266         mAdapter.onSuggestionDismissed(suggestion);
267     }
268 
269     private class SuggestionLoader extends AsyncTask<Void, Void, List<Tile>> {
270         @Override
doInBackground(Void... params)271         protected List<Tile> doInBackground(Void... params) {
272             final Context context = getContext();
273             boolean isSmartSuggestionEnabled =
274                     mSuggestionFeatureProvider.isSmartSuggestionEnabled(context);
275             final SuggestionList sl = mSuggestionParser.getSuggestions(isSmartSuggestionEnabled);
276             final List<Tile> suggestions = sl.getSuggestions();
277 
278             if (isSmartSuggestionEnabled) {
279                 List<String> suggestionIds = new ArrayList<>(suggestions.size());
280                 for (Tile suggestion : suggestions) {
281                     suggestionIds.add(mSuggestionFeatureProvider.getSuggestionIdentifier(
282                             context, suggestion));
283                 }
284                 // TODO: create a Suggestion class to maintain the id and other info
285                 mSuggestionFeatureProvider.rankSuggestions(suggestions, suggestionIds);
286             }
287             for (int i = 0; i < suggestions.size(); i++) {
288                 Tile suggestion = suggestions.get(i);
289                 if (mSuggestionsChecks.isSuggestionComplete(suggestion)) {
290                     suggestions.remove(i--);
291                 }
292             }
293             if (sl.isExclusiveSuggestionCategory()) {
294                 mSuggestionFeatureProvider.filterExclusiveSuggestions(suggestions);
295             }
296             return suggestions;
297         }
298 
299         @Override
onPostExecute(List<Tile> tiles)300         protected void onPostExecute(List<Tile> tiles) {
301             // tell handler that suggestions were loaded quickly enough
302             mHandler.removeCallbacksAndMessages(null);
303             updateCategoryAndSuggestion(tiles);
304         }
305     }
306 
307     @VisibleForTesting
updateCategoryAndSuggestion(List<Tile> suggestions)308     void updateCategoryAndSuggestion(List<Tile> suggestions) {
309         final Activity activity = getActivity();
310         if (activity == null) {
311             return;
312         }
313 
314         final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory(
315                 CategoryKey.CATEGORY_HOMEPAGE);
316         mSummaryLoader.updateSummaryToCache(category);
317         if (suggestions != null) {
318             mAdapter.setCategoriesAndSuggestions(category, suggestions);
319         } else {
320             mAdapter.setCategory(category);
321         }
322     }
323 
324 }
325