Home | History | Annotate | Download | only in dashboard
      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 
     17 package com.android.settings.dashboard;
     18 
     19 import android.content.Context;
     20 import android.os.Bundle;
     21 import android.support.annotation.VisibleForTesting;
     22 import android.support.v14.preference.PreferenceFragment;
     23 import android.support.v7.preference.Preference;
     24 import android.support.v7.preference.PreferenceGroup;
     25 import android.support.v7.preference.PreferenceScreen;
     26 import android.text.TextUtils;
     27 import android.util.Log;
     28 
     29 import com.android.internal.logging.nano.MetricsProto;
     30 import com.android.settings.R;
     31 import com.android.settings.core.instrumentation.Instrumentable;
     32 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
     33 import com.android.settings.core.lifecycle.LifecycleObserver;
     34 import com.android.settings.core.lifecycle.events.OnCreate;
     35 import com.android.settings.core.lifecycle.events.OnSaveInstanceState;
     36 import com.android.settings.overlay.FeatureFactory;
     37 
     38 import java.util.ArrayList;
     39 import java.util.Collections;
     40 import java.util.List;
     41 
     42 public class ProgressiveDisclosureMixin implements Preference.OnPreferenceClickListener,
     43         LifecycleObserver, OnCreate, OnSaveInstanceState {
     44 
     45     private static final String TAG = "ProgressiveDisclosure";
     46     private static final String STATE_USER_EXPANDED = "state_user_expanded";
     47     private static final int DEFAULT_TILE_LIMIT = 300;
     48 
     49     private final Context mContext;
     50     // Collapsed preference sorted by order.
     51     private final List<Preference> mCollapsedPrefs = new ArrayList<>();
     52     private final MetricsFeatureProvider mMetricsFeatureProvider;
     53     private final PreferenceFragment mFragment;
     54     private /* final */ ExpandPreference mExpandButton;
     55 
     56     private int mTileLimit = DEFAULT_TILE_LIMIT;
     57     private boolean mUserExpanded;
     58 
     59     public ProgressiveDisclosureMixin(Context context,
     60             PreferenceFragment fragment, boolean keepExpanded) {
     61         mContext = context;
     62         mFragment = fragment;
     63         mExpandButton = new ExpandPreference(context);
     64         mExpandButton.setOnPreferenceClickListener(this);
     65         mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     66         mUserExpanded = keepExpanded;
     67     }
     68 
     69     @Override
     70     public void onCreate(Bundle savedInstanceState) {
     71         if (savedInstanceState != null) {
     72             mUserExpanded = savedInstanceState.getBoolean(STATE_USER_EXPANDED, false);
     73         }
     74     }
     75 
     76     @Override
     77     public void onSaveInstanceState(Bundle outState) {
     78         outState.putBoolean(STATE_USER_EXPANDED, mUserExpanded);
     79     }
     80 
     81     @Override
     82     public boolean onPreferenceClick(Preference preference) {
     83         if (preference instanceof ExpandPreference) {
     84             final PreferenceScreen screen = mFragment.getPreferenceScreen();
     85             if (screen != null) {
     86                 screen.removePreference(preference);
     87                 for (Preference pref : mCollapsedPrefs) {
     88                     screen.addPreference(pref);
     89                 }
     90                 mCollapsedPrefs.clear();
     91                 mUserExpanded = true;
     92                 final int metricsCategory;
     93                 if (mFragment instanceof Instrumentable) {
     94                     metricsCategory = ((Instrumentable) mFragment).getMetricsCategory();
     95                 } else {
     96                     metricsCategory = MetricsProto.MetricsEvent.VIEW_UNKNOWN;
     97                 }
     98                 mMetricsFeatureProvider.actionWithSource(mContext, metricsCategory,
     99                         MetricsProto.MetricsEvent.ACTION_SETTINGS_ADVANCED_BUTTON_EXPAND);
    100             }
    101         }
    102         return false;
    103     }
    104 
    105     /**
    106      * Sets the threshold to start collapsing preferences when there are too many.
    107      */
    108     public void setTileLimit(int limit) {
    109         mTileLimit = limit;
    110     }
    111 
    112     /**
    113      * Whether the controller is in collapsed state.
    114      */
    115     public boolean isCollapsed() {
    116         return !mCollapsedPrefs.isEmpty();
    117     }
    118 
    119     /**
    120      * Whether the screen should be collapsed.
    121      */
    122     public boolean shouldCollapse(PreferenceScreen screen) {
    123         return !mUserExpanded && screen.getPreferenceCount() > mTileLimit;
    124     }
    125 
    126     /**
    127      * Collapse extra preferences and show a "More" button
    128      */
    129     public void collapse(PreferenceScreen screen) {
    130         final int itemCount = screen.getPreferenceCount();
    131         if (!shouldCollapse(screen)) {
    132             return;
    133         }
    134         if (!mCollapsedPrefs.isEmpty()) {
    135             Log.w(TAG, "collapsed list should ALWAYS BE EMPTY before collapsing!");
    136         }
    137 
    138         for (int i = itemCount - 1; i >= mTileLimit; i--) {
    139             final Preference preference = screen.getPreference(i);
    140             addToCollapsedList(preference);
    141             screen.removePreference(preference);
    142         }
    143         screen.addPreference(mExpandButton);
    144     }
    145 
    146     /**
    147      * Adds preference to screen. If there are too many preference on screen, adds it to
    148      * collapsed list instead.
    149      */
    150     public void addPreference(PreferenceScreen screen, Preference pref) {
    151         // Either add to screen, or to collapsed list.
    152         if (isCollapsed()) {
    153             // insert the preference to right position.
    154             final int lastPreferenceIndex = screen.getPreferenceCount() - 2;
    155             if (lastPreferenceIndex >= 0) {
    156                 final Preference lastPreference = screen.getPreference(lastPreferenceIndex);
    157                 if (lastPreference.getOrder() > pref.getOrder()) {
    158                     // insert to screen and move the last pref to collapsed list.
    159                     screen.removePreference(lastPreference);
    160                     screen.addPreference(pref);
    161                     addToCollapsedList(lastPreference);
    162                 } else {
    163                     // Insert to collapsed list.
    164                     addToCollapsedList(pref);
    165                 }
    166             } else {
    167                 // Couldn't find last preference on screen, just add to collapsed list.
    168                 addToCollapsedList(pref);
    169             }
    170         } else if (shouldCollapse(screen)) {
    171             // About to have too many tiles on scree, collapse and add pref to collapsed list.
    172             screen.addPreference(pref);
    173             collapse(screen);
    174         } else {
    175             // No need to collapse, add to screen directly.
    176             screen.addPreference(pref);
    177         }
    178     }
    179 
    180     /**
    181      * Removes preference. If the preference is on screen, remove it from screen. If the
    182      * preference is in collapsed list, remove it from list.
    183      */
    184     public void removePreference(PreferenceScreen screen, String key) {
    185         // Try removing from screen.
    186         final Preference preference = screen.findPreference(key);
    187         if (preference != null) {
    188             screen.removePreference(preference);
    189             return;
    190         }
    191         // Didn't find on screen, try removing from collapsed list.
    192         for (int i = 0; i < mCollapsedPrefs.size(); i++) {
    193             final Preference pref = mCollapsedPrefs.get(i);
    194             if (TextUtils.equals(key, pref.getKey())) {
    195                 mCollapsedPrefs.remove(pref);
    196                 if (mCollapsedPrefs.isEmpty()) {
    197                     // Removed last element, remove expand button too.
    198                     screen.removePreference(mExpandButton);
    199                 } else {
    200                     updateExpandButtonSummary();
    201                 }
    202                 return;
    203             }
    204         }
    205     }
    206 
    207     /**
    208      * Finds preference by key, either from screen or from collapsed list.
    209      */
    210     public Preference findPreference(PreferenceScreen screen, CharSequence key) {
    211         Preference preference = screen.findPreference(key);
    212         if (preference != null) {
    213             return preference;
    214         }
    215         for (int i = 0; i < mCollapsedPrefs.size(); i++) {
    216             final Preference pref = mCollapsedPrefs.get(i);
    217             if (TextUtils.equals(key, pref.getKey())) {
    218                 return pref;
    219             }
    220             if (pref instanceof PreferenceGroup) {
    221                 final Preference returnedPreference = ((PreferenceGroup) pref).findPreference(key);
    222                 if (returnedPreference != null) {
    223                     return returnedPreference;
    224                 }
    225             }
    226         }
    227         Log.d(TAG, "Cannot find preference with key " + key);
    228         return null;
    229     }
    230 
    231     /**
    232      * Add preference to collapsed list.
    233      */
    234     @VisibleForTesting
    235     void addToCollapsedList(Preference preference) {
    236         // Insert preference based on it's order.
    237         int insertionIndex = Collections.binarySearch(mCollapsedPrefs, preference);
    238         if (insertionIndex < 0) {
    239             insertionIndex = insertionIndex * -1 - 1;
    240         }
    241         mCollapsedPrefs.add(insertionIndex, preference);
    242         updateExpandButtonSummary();
    243     }
    244 
    245     @VisibleForTesting
    246     List<Preference> getCollapsedPrefs() {
    247         return mCollapsedPrefs;
    248     }
    249 
    250     @VisibleForTesting
    251     void updateExpandButtonSummary() {
    252         final int size = mCollapsedPrefs.size();
    253         if (size == 0) {
    254             mExpandButton.setSummary(null);
    255         } else if (size == 1) {
    256             mExpandButton.setSummary(mCollapsedPrefs.get(0).getTitle());
    257         } else {
    258             CharSequence summary = mCollapsedPrefs.get(0).getTitle();
    259             for (int i = 1; i < size; i++) {
    260                 final CharSequence nextSummary = mCollapsedPrefs.get(i).getTitle();
    261                 if (!TextUtils.isEmpty(nextSummary)) {
    262                     summary = mContext.getString(R.string.join_many_items_middle, summary,
    263                             nextSummary);
    264                 }
    265             }
    266             mExpandButton.setSummary(summary);
    267         }
    268     }
    269 }
    270