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.ConditionAdapterUtils; 36 import com.android.settings.dashboard.conditional.ConditionManager; 37 import com.android.settings.dashboard.conditional.FocusRecyclerView; 38 import com.android.settings.dashboard.suggestions.SuggestionDismissController; 39 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; 40 import com.android.settings.dashboard.suggestions.SuggestionsChecks; 41 import com.android.settings.overlay.FeatureFactory; 42 import com.android.settingslib.SuggestionParser; 43 import com.android.settingslib.drawer.CategoryKey; 44 import com.android.settingslib.drawer.DashboardCategory; 45 import com.android.settingslib.drawer.SettingsDrawerActivity; 46 import com.android.settingslib.drawer.Tile; 47 48 import java.util.ArrayList; 49 import java.util.List; 50 51 public class DashboardSummary extends InstrumentedFragment 52 implements SettingsDrawerActivity.CategoryListener, ConditionManager.ConditionListener, 53 FocusRecyclerView.FocusListener { 54 public static final boolean DEBUG = false; 55 private static final boolean DEBUG_TIMING = false; 56 private static final int MAX_WAIT_MILLIS = 700; 57 private static final String TAG = "DashboardSummary"; 58 59 private static final String SUGGESTIONS = "suggestions"; 60 61 private static final String EXTRA_SCROLL_POSITION = "scroll_position"; 62 63 private final Handler mHandler = new Handler(); 64 65 private FocusRecyclerView mDashboard; 66 private DashboardAdapter mAdapter; 67 private SummaryLoader mSummaryLoader; 68 private ConditionManager mConditionManager; 69 private SuggestionParser mSuggestionParser; 70 private LinearLayoutManager mLayoutManager; 71 private SuggestionsChecks mSuggestionsChecks; 72 private DashboardFeatureProvider mDashboardFeatureProvider; 73 private SuggestionFeatureProvider mSuggestionFeatureProvider; 74 private boolean isOnCategoriesChangedCalled; 75 private SuggestionDismissController mSuggestionDismissHandler; 76 77 @Override getMetricsCategory()78 public int getMetricsCategory() { 79 return MetricsEvent.DASHBOARD_SUMMARY; 80 } 81 82 @Override onCreate(Bundle savedInstanceState)83 public void onCreate(Bundle savedInstanceState) { 84 long startTime = System.currentTimeMillis(); 85 super.onCreate(savedInstanceState); 86 final Activity activity = getActivity(); 87 mDashboardFeatureProvider = FeatureFactory.getFactory(activity) 88 .getDashboardFeatureProvider(activity); 89 mSuggestionFeatureProvider = FeatureFactory.getFactory(activity) 90 .getSuggestionFeatureProvider(activity); 91 92 mSummaryLoader = new SummaryLoader(activity, CategoryKey.CATEGORY_HOMEPAGE); 93 94 mConditionManager = ConditionManager.get(activity, false); 95 getLifecycle().addObserver(mConditionManager); 96 mSuggestionParser = new SuggestionParser(activity, 97 activity.getSharedPreferences(SUGGESTIONS, 0), R.xml.suggestion_ordering); 98 mSuggestionsChecks = new SuggestionsChecks(getContext()); 99 if (DEBUG_TIMING) { 100 Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime) 101 + " ms"); 102 } 103 } 104 105 @Override onDestroy()106 public void onDestroy() { 107 mSummaryLoader.release(); 108 super.onDestroy(); 109 } 110 111 @Override onResume()112 public void onResume() { 113 long startTime = System.currentTimeMillis(); 114 super.onResume(); 115 116 ((SettingsDrawerActivity) getActivity()).addCategoryListener(this); 117 mSummaryLoader.setListening(true); 118 final int metricsCategory = getMetricsCategory(); 119 for (Condition c : mConditionManager.getConditions()) { 120 if (c.shouldShow()) { 121 mMetricsFeatureProvider.visible(getContext(), metricsCategory, 122 c.getMetricsConstant()); 123 } 124 } 125 if (DEBUG_TIMING) { 126 Log.d(TAG, "onResume took " + (System.currentTimeMillis() - startTime) + " ms"); 127 } 128 } 129 130 @Override onPause()131 public void onPause() { 132 super.onPause(); 133 134 ((SettingsDrawerActivity) getActivity()).remCategoryListener(this); 135 mSummaryLoader.setListening(false); 136 for (Condition c : mConditionManager.getConditions()) { 137 if (c.shouldShow()) { 138 mMetricsFeatureProvider.hidden(getContext(), c.getMetricsConstant()); 139 } 140 } 141 if (!getActivity().isChangingConfigurations()) { 142 mAdapter.onPause(); 143 } 144 } 145 146 @Override onWindowFocusChanged(boolean hasWindowFocus)147 public void onWindowFocusChanged(boolean hasWindowFocus) { 148 long startTime = System.currentTimeMillis(); 149 if (hasWindowFocus) { 150 Log.d(TAG, "Listening for condition changes"); 151 mConditionManager.addListener(this); 152 Log.d(TAG, "conditions refreshed"); 153 mConditionManager.refreshAll(); 154 } else { 155 Log.d(TAG, "Stopped listening for condition changes"); 156 mConditionManager.remListener(this); 157 } 158 if (DEBUG_TIMING) { 159 Log.d(TAG, "onWindowFocusChanged took " 160 + (System.currentTimeMillis() - startTime) + " ms"); 161 } 162 } 163 164 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)165 public View onCreateView(LayoutInflater inflater, ViewGroup container, 166 Bundle savedInstanceState) { 167 return inflater.inflate(R.layout.dashboard, container, false); 168 } 169 170 @Override onSaveInstanceState(Bundle outState)171 public void onSaveInstanceState(Bundle outState) { 172 super.onSaveInstanceState(outState); 173 if (mLayoutManager == null) return; 174 outState.putInt(EXTRA_SCROLL_POSITION, mLayoutManager.findFirstVisibleItemPosition()); 175 if (mAdapter != null) { 176 mAdapter.onSaveInstanceState(outState); 177 } 178 } 179 180 @Override onViewCreated(View view, Bundle bundle)181 public void onViewCreated(View view, Bundle bundle) { 182 long startTime = System.currentTimeMillis(); 183 mDashboard = view.findViewById(R.id.dashboard_container); 184 mLayoutManager = new LinearLayoutManager(getContext()); 185 mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); 186 if (bundle != null) { 187 int scrollPosition = bundle.getInt(EXTRA_SCROLL_POSITION); 188 mLayoutManager.scrollToPosition(scrollPosition); 189 } 190 mDashboard.setLayoutManager(mLayoutManager); 191 mDashboard.setHasFixedSize(true); 192 mDashboard.addItemDecoration(new DashboardDecorator(getContext())); 193 mDashboard.setListener(this); 194 Log.d(TAG, "adapter created"); 195 mAdapter = new DashboardAdapter(getContext(), bundle, mConditionManager.getConditions()); 196 mDashboard.setAdapter(mAdapter); 197 mSuggestionDismissHandler = new SuggestionDismissController( 198 getContext(), mDashboard, mSuggestionParser, mAdapter); 199 mDashboard.setItemAnimator(new DashboardItemAnimator()); 200 mSummaryLoader.setSummaryConsumer(mAdapter); 201 ConditionAdapterUtils.addDismiss(mDashboard); 202 if (DEBUG_TIMING) { 203 Log.d(TAG, "onViewCreated took " 204 + (System.currentTimeMillis() - startTime) + " ms"); 205 } 206 rebuildUI(); 207 } 208 209 @VisibleForTesting rebuildUI()210 void rebuildUI() { 211 new SuggestionLoader().execute(); 212 // Set categories on their own if loading suggestions takes too long. 213 mHandler.postDelayed(() -> { 214 updateCategoryAndSuggestion(null /* tiles */); 215 }, MAX_WAIT_MILLIS); 216 } 217 218 @Override onCategoriesChanged()219 public void onCategoriesChanged() { 220 // Bypass rebuildUI() on the first call of onCategoriesChanged, since rebuildUI() happens 221 // in onViewCreated as well when app starts. But, on the subsequent calls we need to 222 // rebuildUI() because there might be some changes to suggestions and categories. 223 if (isOnCategoriesChangedCalled) { 224 rebuildUI(); 225 } 226 isOnCategoriesChangedCalled = true; 227 } 228 229 @Override onConditionsChanged()230 public void onConditionsChanged() { 231 Log.d(TAG, "onConditionsChanged"); 232 final boolean scrollToTop = mLayoutManager.findFirstCompletelyVisibleItemPosition() <= 1; 233 mAdapter.setConditions(mConditionManager.getConditions()); 234 if (scrollToTop) { 235 mDashboard.scrollToPosition(0); 236 } 237 } 238 239 private class SuggestionLoader extends AsyncTask<Void, Void, List<Tile>> { 240 @Override doInBackground(Void... params)241 protected List<Tile> doInBackground(Void... params) { 242 final Context context = getContext(); 243 boolean isSmartSuggestionEnabled = 244 mSuggestionFeatureProvider.isSmartSuggestionEnabled(context); 245 List<Tile> suggestions = mSuggestionParser.getSuggestions(isSmartSuggestionEnabled); 246 if (isSmartSuggestionEnabled) { 247 List<String> suggestionIds = new ArrayList<>(suggestions.size()); 248 for (Tile suggestion : suggestions) { 249 suggestionIds.add(mSuggestionFeatureProvider.getSuggestionIdentifier( 250 context, suggestion)); 251 } 252 // TODO: create a Suggestion class to maintain the id and other info 253 mSuggestionFeatureProvider.rankSuggestions(suggestions, suggestionIds); 254 } 255 for (int i = 0; i < suggestions.size(); i++) { 256 Tile suggestion = suggestions.get(i); 257 if (mSuggestionsChecks.isSuggestionComplete(suggestion)) { 258 mSuggestionFeatureProvider.dismissSuggestion( 259 context, mSuggestionParser, suggestion); 260 suggestions.remove(i--); 261 } 262 } 263 return suggestions; 264 } 265 266 @Override onPostExecute(List<Tile> tiles)267 protected void onPostExecute(List<Tile> tiles) { 268 // tell handler that suggestions were loaded quickly enough 269 mHandler.removeCallbacksAndMessages(null); 270 updateCategoryAndSuggestion(tiles); 271 } 272 } 273 274 @VisibleForTesting updateCategoryAndSuggestion(List<Tile> suggestions)275 void updateCategoryAndSuggestion(List<Tile> suggestions) { 276 final Activity activity = getActivity(); 277 if (activity == null) { 278 return; 279 } 280 281 // Temporary hack to wrap homepage category into a list. Soon we will create adapter 282 // API that takes a single category. 283 List<DashboardCategory> categories = new ArrayList<>(); 284 categories.add(mDashboardFeatureProvider.getTilesForCategory( 285 CategoryKey.CATEGORY_HOMEPAGE)); 286 if (suggestions != null) { 287 mAdapter.setCategoriesAndSuggestions(categories, suggestions); 288 } else { 289 mAdapter.setCategory(categories); 290 } 291 } 292 } 293