| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings.dashboard; |
| |
| import android.app.Activity; |
| import android.content.Context; |
| import android.os.AsyncTask; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.support.annotation.VisibleForTesting; |
| import android.support.v7.widget.LinearLayoutManager; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| |
| import com.android.internal.logging.nano.MetricsProto.MetricsEvent; |
| import com.android.settings.R; |
| import com.android.settings.core.InstrumentedFragment; |
| import com.android.settings.dashboard.conditional.Condition; |
| import com.android.settings.dashboard.conditional.ConditionAdapterUtils; |
| import com.android.settings.dashboard.conditional.ConditionManager; |
| import com.android.settings.dashboard.conditional.ConditionManager.ConditionListener; |
| import com.android.settings.dashboard.conditional.FocusRecyclerView; |
| import com.android.settings.dashboard.conditional.FocusRecyclerView.FocusListener; |
| import com.android.settings.dashboard.suggestions.SuggestionDismissController; |
| import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; |
| import com.android.settings.dashboard.suggestions.SuggestionsChecks; |
| import com.android.settings.overlay.FeatureFactory; |
| import com.android.settingslib.drawer.CategoryKey; |
| import com.android.settingslib.drawer.DashboardCategory; |
| import com.android.settingslib.drawer.SettingsDrawerActivity; |
| import com.android.settingslib.drawer.SettingsDrawerActivity.CategoryListener; |
| import com.android.settingslib.drawer.Tile; |
| import com.android.settingslib.suggestions.SuggestionList; |
| import com.android.settingslib.suggestions.SuggestionParser; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public class DashboardSummary extends InstrumentedFragment |
| implements CategoryListener, ConditionListener, |
| FocusListener, SuggestionDismissController.Callback { |
| public static final boolean DEBUG = false; |
| private static final boolean DEBUG_TIMING = false; |
| private static final int MAX_WAIT_MILLIS = 700; |
| private static final String TAG = "DashboardSummary"; |
| |
| private static final String SUGGESTIONS = "suggestions"; |
| |
| private static final String EXTRA_SCROLL_POSITION = "scroll_position"; |
| |
| private final Handler mHandler = new Handler(); |
| |
| private FocusRecyclerView mDashboard; |
| private DashboardAdapter mAdapter; |
| private SummaryLoader mSummaryLoader; |
| private ConditionManager mConditionManager; |
| private SuggestionParser mSuggestionParser; |
| private LinearLayoutManager mLayoutManager; |
| private SuggestionsChecks mSuggestionsChecks; |
| private DashboardFeatureProvider mDashboardFeatureProvider; |
| private SuggestionFeatureProvider mSuggestionFeatureProvider; |
| private boolean isOnCategoriesChangedCalled; |
| private SuggestionDismissController mSuggestionDismissHandler; |
| |
| @Override |
| public int getMetricsCategory() { |
| return MetricsEvent.DASHBOARD_SUMMARY; |
| } |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| long startTime = System.currentTimeMillis(); |
| super.onCreate(savedInstanceState); |
| final Activity activity = getActivity(); |
| mDashboardFeatureProvider = FeatureFactory.getFactory(activity) |
| .getDashboardFeatureProvider(activity); |
| mSuggestionFeatureProvider = FeatureFactory.getFactory(activity) |
| .getSuggestionFeatureProvider(activity); |
| |
| mSummaryLoader = new SummaryLoader(activity, CategoryKey.CATEGORY_HOMEPAGE); |
| |
| mConditionManager = ConditionManager.get(activity, false); |
| getLifecycle().addObserver(mConditionManager); |
| mSuggestionParser = new SuggestionParser(activity, |
| activity.getSharedPreferences(SUGGESTIONS, 0), R.xml.suggestion_ordering); |
| mSuggestionsChecks = new SuggestionsChecks(getContext()); |
| if (DEBUG_TIMING) { |
| Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime) |
| + " ms"); |
| } |
| } |
| |
| @Override |
| public void onDestroy() { |
| mSummaryLoader.release(); |
| super.onDestroy(); |
| } |
| |
| @Override |
| public void onResume() { |
| long startTime = System.currentTimeMillis(); |
| super.onResume(); |
| |
| ((SettingsDrawerActivity) getActivity()).addCategoryListener(this); |
| mSummaryLoader.setListening(true); |
| final int metricsCategory = getMetricsCategory(); |
| for (Condition c : mConditionManager.getConditions()) { |
| if (c.shouldShow()) { |
| mMetricsFeatureProvider.visible(getContext(), metricsCategory, |
| c.getMetricsConstant()); |
| } |
| } |
| if (DEBUG_TIMING) { |
| Log.d(TAG, "onResume took " + (System.currentTimeMillis() - startTime) + " ms"); |
| } |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| |
| ((SettingsDrawerActivity) getActivity()).remCategoryListener(this); |
| mSummaryLoader.setListening(false); |
| for (Condition c : mConditionManager.getConditions()) { |
| if (c.shouldShow()) { |
| mMetricsFeatureProvider.hidden(getContext(), c.getMetricsConstant()); |
| } |
| } |
| if (!getActivity().isChangingConfigurations()) { |
| mAdapter.onPause(); |
| } |
| } |
| |
| @Override |
| public void onWindowFocusChanged(boolean hasWindowFocus) { |
| long startTime = System.currentTimeMillis(); |
| if (hasWindowFocus) { |
| Log.d(TAG, "Listening for condition changes"); |
| mConditionManager.addListener(this); |
| Log.d(TAG, "conditions refreshed"); |
| mConditionManager.refreshAll(); |
| } else { |
| Log.d(TAG, "Stopped listening for condition changes"); |
| mConditionManager.remListener(this); |
| } |
| if (DEBUG_TIMING) { |
| Log.d(TAG, "onWindowFocusChanged took " |
| + (System.currentTimeMillis() - startTime) + " ms"); |
| } |
| } |
| |
| @Override |
| public View onCreateView(LayoutInflater inflater, ViewGroup container, |
| Bundle savedInstanceState) { |
| return inflater.inflate(R.layout.dashboard, container, false); |
| } |
| |
| @Override |
| public void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| if (mLayoutManager == null) return; |
| outState.putInt(EXTRA_SCROLL_POSITION, mLayoutManager.findFirstVisibleItemPosition()); |
| if (mAdapter != null) { |
| mAdapter.onSaveInstanceState(outState); |
| } |
| } |
| |
| @Override |
| public void onViewCreated(View view, Bundle bundle) { |
| long startTime = System.currentTimeMillis(); |
| mDashboard = view.findViewById(R.id.dashboard_container); |
| mLayoutManager = new LinearLayoutManager(getContext()); |
| mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); |
| if (bundle != null) { |
| int scrollPosition = bundle.getInt(EXTRA_SCROLL_POSITION); |
| mLayoutManager.scrollToPosition(scrollPosition); |
| } |
| mDashboard.setLayoutManager(mLayoutManager); |
| mDashboard.setHasFixedSize(true); |
| mDashboard.addItemDecoration(new DashboardDecorator(getContext())); |
| mDashboard.setListener(this); |
| Log.d(TAG, "adapter created"); |
| mAdapter = new DashboardAdapter(getContext(), bundle, mConditionManager.getConditions()); |
| mDashboard.setAdapter(mAdapter); |
| mSuggestionDismissHandler = new SuggestionDismissController( |
| getContext(), mDashboard, mSuggestionParser, this); |
| mDashboard.setItemAnimator(new DashboardItemAnimator()); |
| mSummaryLoader.setSummaryConsumer(mAdapter); |
| ConditionAdapterUtils.addDismiss(mDashboard); |
| if (DEBUG_TIMING) { |
| Log.d(TAG, "onViewCreated took " |
| + (System.currentTimeMillis() - startTime) + " ms"); |
| } |
| rebuildUI(); |
| } |
| |
| @VisibleForTesting |
| void rebuildUI() { |
| new SuggestionLoader().execute(); |
| // Set categories on their own if loading suggestions takes too long. |
| mHandler.postDelayed(() -> { |
| updateCategoryAndSuggestion(null /* tiles */); |
| }, MAX_WAIT_MILLIS); |
| } |
| |
| @Override |
| public void onCategoriesChanged() { |
| // Bypass rebuildUI() on the first call of onCategoriesChanged, since rebuildUI() happens |
| // in onViewCreated as well when app starts. But, on the subsequent calls we need to |
| // rebuildUI() because there might be some changes to suggestions and categories. |
| if (isOnCategoriesChangedCalled) { |
| rebuildUI(); |
| } |
| isOnCategoriesChangedCalled = true; |
| } |
| |
| @Override |
| public void onConditionsChanged() { |
| Log.d(TAG, "onConditionsChanged"); |
| final boolean scrollToTop = mLayoutManager.findFirstCompletelyVisibleItemPosition() <= 1; |
| mAdapter.setConditions(mConditionManager.getConditions()); |
| if (scrollToTop) { |
| mDashboard.scrollToPosition(0); |
| } |
| } |
| |
| @Override |
| public Tile getSuggestionForPosition(int position) { |
| return (Tile) mAdapter.getItem(mAdapter.getItemId(position)); |
| } |
| |
| @Override |
| public void onSuggestionDismissed(Tile suggestion) { |
| // Refresh the UI to pick up suggestions that can now be shown because, say, a higher |
| // priority suggestion has been dismissed, or an exclusive suggestion category is emptied. |
| rebuildUI(); |
| } |
| |
| private class SuggestionLoader extends AsyncTask<Void, Void, List<Tile>> { |
| @Override |
| protected List<Tile> doInBackground(Void... params) { |
| final Context context = getContext(); |
| boolean isSmartSuggestionEnabled = |
| mSuggestionFeatureProvider.isSmartSuggestionEnabled(context); |
| final SuggestionList sl = mSuggestionParser.getSuggestions(isSmartSuggestionEnabled); |
| final List<Tile> suggestions = sl.getSuggestions(); |
| |
| if (isSmartSuggestionEnabled) { |
| List<String> suggestionIds = new ArrayList<>(suggestions.size()); |
| for (Tile suggestion : suggestions) { |
| suggestionIds.add(mSuggestionFeatureProvider.getSuggestionIdentifier( |
| context, suggestion)); |
| } |
| // TODO: create a Suggestion class to maintain the id and other info |
| mSuggestionFeatureProvider.rankSuggestions(suggestions, suggestionIds); |
| } |
| for (int i = 0; i < suggestions.size(); i++) { |
| Tile suggestion = suggestions.get(i); |
| if (mSuggestionsChecks.isSuggestionComplete(suggestion)) { |
| mSuggestionFeatureProvider.dismissSuggestion( |
| context, mSuggestionParser, suggestion); |
| suggestions.remove(i--); |
| } |
| } |
| if (sl.isExclusiveSuggestionCategory()) { |
| mSuggestionFeatureProvider.filterExclusiveSuggestions(suggestions); |
| } |
| return suggestions; |
| } |
| |
| @Override |
| protected void onPostExecute(List<Tile> tiles) { |
| // tell handler that suggestions were loaded quickly enough |
| mHandler.removeCallbacksAndMessages(null); |
| updateCategoryAndSuggestion(tiles); |
| } |
| } |
| |
| @VisibleForTesting |
| void updateCategoryAndSuggestion(List<Tile> suggestions) { |
| final Activity activity = getActivity(); |
| if (activity == null) { |
| return; |
| } |
| |
| // Temporary hack to wrap homepage category into a list. Soon we will create adapter |
| // API that takes a single category. |
| List<DashboardCategory> categories = new ArrayList<>(); |
| categories.add(mDashboardFeatureProvider.getTilesForCategory( |
| CategoryKey.CATEGORY_HOMEPAGE)); |
| if (suggestions != null) { |
| mAdapter.setCategoriesAndSuggestions(categories, suggestions); |
| } else { |
| mAdapter.setCategory(categories); |
| } |
| } |
| } |