Home | History | Annotate | Download | only in dashboard
      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
     81     public int getMetricsCategory() {
     82         return MetricsEvent.DASHBOARD_SUMMARY;
     83     }
     84 
     85     @Override
     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
    111     public void onDestroy() {
    112         mSummaryLoader.release();
    113         super.onDestroy();
    114     }
    115 
    116     @Override
    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
    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
    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
    170     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    171             Bundle savedInstanceState) {
    172         return inflater.inflate(R.layout.dashboard, container, false);
    173     }
    174 
    175     @Override
    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
    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
    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
    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
    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
    260     public Tile getSuggestionForPosition(int position) {
    261         return mAdapter.getSuggestion(position);
    262     }
    263 
    264     @Override
    265     public void onSuggestionDismissed(Tile suggestion) {
    266         mAdapter.onSuggestionDismissed(suggestion);
    267     }
    268 
    269     private class SuggestionLoader extends AsyncTask<Void, Void, List<Tile>> {
    270         @Override
    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
    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
    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