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 package com.android.settings.dashboard; 17 18 import android.app.Activity; 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.os.Bundle; 22 import android.support.annotation.VisibleForTesting; 23 import android.support.v7.preference.Preference; 24 import android.support.v7.preference.PreferenceManager; 25 import android.support.v7.preference.PreferenceScreen; 26 import android.text.TextUtils; 27 import android.util.ArrayMap; 28 import android.util.ArraySet; 29 import android.util.Log; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.ViewGroup; 33 34 import com.android.settings.SettingsPreferenceFragment; 35 import com.android.settings.core.PreferenceController; 36 import com.android.settings.overlay.FeatureFactory; 37 import com.android.settings.search.Indexable; 38 import com.android.settingslib.drawer.DashboardCategory; 39 import com.android.settingslib.drawer.SettingsDrawerActivity; 40 import com.android.settingslib.drawer.Tile; 41 42 import java.util.ArrayList; 43 import java.util.Collection; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.Set; 47 48 /** 49 * Base fragment for dashboard style UI containing a list of static and dynamic setting items. 50 */ 51 public abstract class DashboardFragment extends SettingsPreferenceFragment 52 implements SettingsDrawerActivity.CategoryListener, Indexable, 53 SummaryLoader.SummaryConsumer { 54 private static final String TAG = "DashboardFragment"; 55 56 private final Map<Class, PreferenceController> mPreferenceControllers = 57 new ArrayMap<>(); 58 private final Set<String> mDashboardTilePrefKeys = new ArraySet<>(); 59 60 protected ProgressiveDisclosureMixin mProgressiveDisclosureMixin; 61 protected DashboardFeatureProvider mDashboardFeatureProvider; 62 private DashboardTilePlaceholderPreferenceController mPlaceholderPreferenceController; 63 private boolean mListeningToCategoryChange; 64 private SummaryLoader mSummaryLoader; 65 66 @Override onAttach(Context context)67 public void onAttach(Context context) { 68 super.onAttach(context); 69 mDashboardFeatureProvider = 70 FeatureFactory.getFactory(context).getDashboardFeatureProvider(context); 71 mProgressiveDisclosureMixin = mDashboardFeatureProvider 72 .getProgressiveDisclosureMixin(context, this, getArguments()); 73 getLifecycle().addObserver(mProgressiveDisclosureMixin); 74 75 List<PreferenceController> controllers = getPreferenceControllers(context); 76 if (controllers == null) { 77 controllers = new ArrayList<>(); 78 } 79 mPlaceholderPreferenceController = 80 new DashboardTilePlaceholderPreferenceController(context); 81 controllers.add(mPlaceholderPreferenceController); 82 for (PreferenceController controller : controllers) { 83 addPreferenceController(controller); 84 } 85 } 86 87 @Override onCreate(Bundle icicle)88 public void onCreate(Bundle icicle) { 89 super.onCreate(icicle); 90 // Set ComparisonCallback so we get better animation when list changes. 91 getPreferenceManager().setPreferenceComparisonCallback( 92 new PreferenceManager.SimplePreferenceComparisonCallback()); 93 } 94 95 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)96 public View onCreateView(LayoutInflater inflater, ViewGroup container, 97 Bundle savedInstanceState) { 98 final View view = super.onCreateView(inflater, container, savedInstanceState); 99 return view; 100 } 101 102 @Override onCategoriesChanged()103 public void onCategoriesChanged() { 104 final DashboardCategory category = 105 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey()); 106 if (category == null) { 107 return; 108 } 109 refreshDashboardTiles(getLogTag()); 110 } 111 112 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)113 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 114 super.onCreatePreferences(savedInstanceState, rootKey); 115 refreshAllPreferences(getLogTag()); 116 } 117 118 @Override onStart()119 public void onStart() { 120 super.onStart(); 121 final DashboardCategory category = 122 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey()); 123 if (category == null) { 124 return; 125 } 126 if (mSummaryLoader != null) { 127 // SummaryLoader can be null when there is no dynamic tiles. 128 mSummaryLoader.setListening(true); 129 } 130 final Activity activity = getActivity(); 131 if (activity instanceof SettingsDrawerActivity) { 132 mListeningToCategoryChange = true; 133 ((SettingsDrawerActivity) activity).addCategoryListener(this); 134 } 135 } 136 137 @Override notifySummaryChanged(Tile tile)138 public void notifySummaryChanged(Tile tile) { 139 final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile); 140 final Preference pref = mProgressiveDisclosureMixin.findPreference( 141 getPreferenceScreen(), key); 142 if (pref == null) { 143 Log.d(getLogTag(), 144 String.format("Can't find pref by key %s, skipping update summary %s/%s", 145 key, tile.title, tile.summary)); 146 return; 147 } 148 pref.setSummary(tile.summary); 149 } 150 151 @Override onResume()152 public void onResume() { 153 super.onResume(); 154 updatePreferenceStates(); 155 } 156 157 @Override onPreferenceTreeClick(Preference preference)158 public boolean onPreferenceTreeClick(Preference preference) { 159 Collection<PreferenceController> controllers = mPreferenceControllers.values(); 160 // If preference contains intent, log it before handling. 161 mMetricsFeatureProvider.logDashboardStartIntent( 162 getContext(), preference.getIntent(), getMetricsCategory()); 163 // Give all controllers a chance to handle click. 164 for (PreferenceController controller : controllers) { 165 if (controller.handlePreferenceTreeClick(preference)) { 166 return true; 167 } 168 } 169 return super.onPreferenceTreeClick(preference); 170 } 171 172 @Override onStop()173 public void onStop() { 174 super.onStop(); 175 if (mSummaryLoader != null) { 176 // SummaryLoader can be null when there is no dynamic tiles. 177 mSummaryLoader.setListening(false); 178 } 179 if (mListeningToCategoryChange) { 180 final Activity activity = getActivity(); 181 if (activity instanceof SettingsDrawerActivity) { 182 ((SettingsDrawerActivity) activity).remCategoryListener(this); 183 } 184 mListeningToCategoryChange = false; 185 } 186 } 187 getPreferenceController(Class<T> clazz)188 protected <T extends PreferenceController> T getPreferenceController(Class<T> clazz) { 189 PreferenceController controller = mPreferenceControllers.get(clazz); 190 return (T) controller; 191 } 192 addPreferenceController(PreferenceController controller)193 protected void addPreferenceController(PreferenceController controller) { 194 mPreferenceControllers.put(controller.getClass(), controller); 195 } 196 197 /** 198 * Returns the CategoryKey for loading {@link DashboardCategory} for this fragment. 199 */ 200 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) getCategoryKey()201 public String getCategoryKey() { 202 return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName()); 203 } 204 205 /** 206 * Get the tag string for logging. 207 */ getLogTag()208 protected abstract String getLogTag(); 209 210 /** 211 * Get the res id for static preference xml for this fragment. 212 */ getPreferenceScreenResId()213 protected abstract int getPreferenceScreenResId(); 214 215 /** 216 * Get a list of {@link PreferenceController} for this fragment. 217 */ getPreferenceControllers(Context context)218 protected abstract List<PreferenceController> getPreferenceControllers(Context context); 219 220 /** 221 * Returns true if this tile should be displayed 222 */ displayTile(Tile tile)223 protected boolean displayTile(Tile tile) { 224 return true; 225 } 226 227 /** 228 * Displays resource based tiles. 229 */ displayResourceTiles()230 private void displayResourceTiles() { 231 final int resId = getPreferenceScreenResId(); 232 if (resId <= 0) { 233 return; 234 } 235 addPreferencesFromResource(resId); 236 final PreferenceScreen screen = getPreferenceScreen(); 237 Collection<PreferenceController> controllers = mPreferenceControllers.values(); 238 for (PreferenceController controller : controllers) { 239 controller.displayPreference(screen); 240 } 241 } 242 243 /** 244 * Update state of each preference managed by PreferenceController. 245 */ updatePreferenceStates()246 protected void updatePreferenceStates() { 247 Collection<PreferenceController> controllers = mPreferenceControllers.values(); 248 final PreferenceScreen screen = getPreferenceScreen(); 249 for (PreferenceController controller : controllers) { 250 if (!controller.isAvailable()) { 251 continue; 252 } 253 final String key = controller.getPreferenceKey(); 254 255 final Preference preference = mProgressiveDisclosureMixin.findPreference(screen, key); 256 if (preference == null) { 257 Log.d(TAG, String.format("Cannot find preference with key %s in Controller %s", 258 key, controller.getClass().getSimpleName())); 259 continue; 260 } 261 controller.updateState(preference); 262 } 263 } 264 265 /** 266 * Refresh all preference items, including both static prefs from xml, and dynamic items from 267 * DashboardCategory. 268 */ refreshAllPreferences(final String TAG)269 private void refreshAllPreferences(final String TAG) { 270 // First remove old preferences. 271 if (getPreferenceScreen() != null) { 272 // Intentionally do not cache PreferenceScreen because it will be recreated later. 273 getPreferenceScreen().removeAll(); 274 } 275 276 // Add resource based tiles. 277 displayResourceTiles(); 278 mProgressiveDisclosureMixin.collapse(getPreferenceScreen()); 279 280 refreshDashboardTiles(TAG); 281 } 282 283 /** 284 * Refresh preference items backed by DashboardCategory. 285 */ 286 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) refreshDashboardTiles(final String TAG)287 void refreshDashboardTiles(final String TAG) { 288 final PreferenceScreen screen = getPreferenceScreen(); 289 290 final DashboardCategory category = 291 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey()); 292 if (category == null) { 293 Log.d(TAG, "NO dashboard tiles for " + TAG); 294 return; 295 } 296 List<Tile> tiles = category.tiles; 297 if (tiles == null) { 298 Log.d(TAG, "tile list is empty, skipping category " + category.title); 299 return; 300 } 301 // Create a list to track which tiles are to be removed. 302 final List<String> remove = new ArrayList<>(mDashboardTilePrefKeys); 303 304 // There are dashboard tiles, so we need to install SummaryLoader. 305 if (mSummaryLoader != null) { 306 mSummaryLoader.release(); 307 } 308 final Context context = getContext(); 309 mSummaryLoader = new SummaryLoader(getActivity(), getCategoryKey()); 310 mSummaryLoader.setSummaryConsumer(this); 311 final TypedArray a = context.obtainStyledAttributes(new int[]{ 312 android.R.attr.colorControlNormal}); 313 final int tintColor = a.getColor(0, context.getColor(android.R.color.white)); 314 a.recycle(); 315 final String pkgName = context.getPackageName(); 316 // Install dashboard tiles. 317 for (Tile tile : tiles) { 318 final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile); 319 if (TextUtils.isEmpty(key)) { 320 Log.d(TAG, "tile does not contain a key, skipping " + tile); 321 continue; 322 } 323 if (!displayTile(tile)) { 324 continue; 325 } 326 if (pkgName != null && tile.intent != null 327 && !pkgName.equals(tile.intent.getComponent().getPackageName())) { 328 // If this drawable is coming from outside Settings, tint it to match the color. 329 tile.icon.setTint(tintColor); 330 } 331 if (mDashboardTilePrefKeys.contains(key)) { 332 // Have the key already, will rebind. 333 final Preference preference = mProgressiveDisclosureMixin.findPreference( 334 screen, key); 335 mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), getMetricsCategory(), 336 preference, tile, key, mPlaceholderPreferenceController.getOrder()); 337 } else { 338 // Don't have this key, add it. 339 final Preference pref = new Preference(getPrefContext()); 340 mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), getMetricsCategory(), 341 pref, tile, key, mPlaceholderPreferenceController.getOrder()); 342 mProgressiveDisclosureMixin.addPreference(screen, pref); 343 mDashboardTilePrefKeys.add(key); 344 } 345 remove.remove(key); 346 } 347 // Finally remove tiles that are gone. 348 for (String key : remove) { 349 mDashboardTilePrefKeys.remove(key); 350 mProgressiveDisclosureMixin.removePreference(screen, key); 351 } 352 mSummaryLoader.setListening(true); 353 } 354 } 355