blob: 9d1ed7c6bea5fdb756044eafd5493679097efef2 [file] [log] [blame]
Fan Zhangbb6d2602016-10-04 13:21:06 -07001/*
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 */
16package com.android.settings.dashboard;
17
18import android.app.Activity;
Fan Zhangff47b302018-11-09 14:52:42 -080019import android.app.settings.SettingsEnums;
Jason Chiubac59872019-10-24 17:30:34 +080020import android.content.ContentResolver;
Fan Zhangbb6d2602016-10-04 13:21:06 -070021import android.content.Context;
Fan Zhang36924652016-10-07 08:38:48 -070022import android.os.Bundle;
Fan Zhangbb6d2602016-10-04 13:21:06 -070023import android.text.TextUtils;
24import android.util.ArrayMap;
25import android.util.Log;
26
Yanting Yang4e56cb22019-04-18 21:37:31 +080027import androidx.annotation.CallSuper;
Fan Zhang1c61a582018-07-26 11:26:11 -070028import androidx.annotation.VisibleForTesting;
Chaohui Wang70470582022-03-29 14:37:53 +080029import androidx.lifecycle.LifecycleObserver;
Fan Zhang1c61a582018-07-26 11:26:11 -070030import androidx.preference.Preference;
Doris Ling8741c042018-08-27 14:11:55 -070031import androidx.preference.PreferenceGroup;
Fan Zhang1c61a582018-07-26 11:26:11 -070032import androidx.preference.PreferenceManager;
33import androidx.preference.PreferenceScreen;
Jason Chiubac59872019-10-24 17:30:34 +080034import androidx.preference.SwitchPreference;
Fan Zhang1c61a582018-07-26 11:26:11 -070035
Yanting Yang4e56cb22019-04-18 21:37:31 +080036import com.android.settings.R;
Fan Zhangbb6d2602016-10-04 13:21:06 -070037import com.android.settings.SettingsPreferenceFragment;
Fan Zhang917f1012018-02-21 15:22:25 -080038import com.android.settings.core.BasePreferenceController;
Jason Chiuc713c3e2021-06-17 17:48:02 +080039import com.android.settings.core.CategoryMixin.CategoryHandler;
40import com.android.settings.core.CategoryMixin.CategoryListener;
Fan Zhang917f1012018-02-21 15:22:25 -080041import com.android.settings.core.PreferenceControllerListHelper;
Fan Zhangbb6d2602016-10-04 13:21:06 -070042import com.android.settings.overlay.FeatureFactory;
Wilson Wu9edf7972021-08-27 10:19:56 +080043import com.android.settingslib.PrimarySwitchPreference;
Tony Mantler1d583e12017-06-13 13:09:25 -070044import com.android.settingslib.core.AbstractPreferenceController;
Fan Zhang917f1012018-02-21 15:22:25 -080045import com.android.settingslib.core.lifecycle.Lifecycle;
Fan Zhangbb6d2602016-10-04 13:21:06 -070046import com.android.settingslib.drawer.DashboardCategory;
Jason Chiubac59872019-10-24 17:30:34 +080047import com.android.settingslib.drawer.ProviderTile;
Fan Zhangbb6d2602016-10-04 13:21:06 -070048import com.android.settingslib.drawer.Tile;
Raff Tsai6db277e2019-10-02 16:43:23 +080049import com.android.settingslib.search.Indexable;
Fan Zhangbb6d2602016-10-04 13:21:06 -070050
Fan Zhanga1a84e62016-10-19 14:15:34 -070051import java.util.ArrayList;
Yanting Yang4e56cb22019-04-18 21:37:31 +080052import java.util.Arrays;
Fan Zhangbb6d2602016-10-04 13:21:06 -070053import java.util.Collection;
Christian Göllnerff9065a2022-02-17 11:32:54 +000054import java.util.Collections;
Fan Zhangbb6d2602016-10-04 13:21:06 -070055import java.util.List;
56import java.util.Map;
Jason Chiubac59872019-10-24 17:30:34 +080057import java.util.Objects;
Jason Chiu20df25e2020-09-30 14:08:33 +080058import java.util.Set;
Jason Chiuc9615612022-05-06 11:14:54 +080059import java.util.concurrent.CountDownLatch;
60import java.util.concurrent.TimeUnit;
Fan Zhangbb6d2602016-10-04 13:21:06 -070061
62/**
63 * Base fragment for dashboard style UI containing a list of static and dynamic setting items.
64 */
65public abstract class DashboardFragment extends SettingsPreferenceFragment
Jason Chiuc713c3e2021-06-17 17:48:02 +080066 implements CategoryListener, Indexable, PreferenceGroup.OnExpandButtonClickListener,
jackqdyulei22904a02018-12-27 14:40:09 -080067 BasePreferenceController.UiBlockListener {
Raff Tsaif71db732019-12-02 10:55:29 +080068 public static final String CATEGORY = "category";
Fan Zhangdb1112a2016-10-18 12:58:31 -070069 private static final String TAG = "DashboardFragment";
Jason Chiuc9615612022-05-06 11:14:54 +080070 private static final long TIMEOUT_MILLIS = 50L;
Fan Zhangbb6d2602016-10-04 13:21:06 -070071
Jason Chiubac59872019-10-24 17:30:34 +080072 @VisibleForTesting
73 final ArrayMap<String, List<DynamicDataObserver>> mDashboardTilePrefKeys = new ArrayMap<>();
Ben Lin92751772017-12-21 17:32:34 -080074 private final Map<Class, List<AbstractPreferenceController>> mPreferenceControllers =
Fan Zhangbb6d2602016-10-04 13:21:06 -070075 new ArrayMap<>();
Jason Chiubac59872019-10-24 17:30:34 +080076 private final List<DynamicDataObserver> mRegisteredObservers = new ArrayList<>();
hughchend49de612019-09-24 10:30:49 +080077 private final List<AbstractPreferenceController> mControllers = new ArrayList<>();
Sunny Shaoe5e82402019-11-27 18:55:16 +080078 @VisibleForTesting
79 UiBlockerController mBlockerController;
Fan Zhangb297abc2018-02-21 09:37:10 -080080 private DashboardFeatureProvider mDashboardFeatureProvider;
Fan Zhang9dc9c612016-11-11 13:23:21 -080081 private DashboardTilePlaceholderPreferenceController mPlaceholderPreferenceController;
Fan Zhang36924652016-10-07 08:38:48 -070082 private boolean mListeningToCategoryChange;
Yanting Yang4e56cb22019-04-18 21:37:31 +080083 private List<String> mSuppressInjectedTileKeys;
Fan Zhangbb6d2602016-10-04 13:21:06 -070084
85 @Override
86 public void onAttach(Context context) {
87 super.onAttach(context);
Yanting Yang4e56cb22019-04-18 21:37:31 +080088 mSuppressInjectedTileKeys = Arrays.asList(context.getResources().getStringArray(
89 R.array.config_suppress_injected_tile_keys));
Fan Zhang917f1012018-02-21 15:22:25 -080090 mDashboardFeatureProvider = FeatureFactory.getFactory(context).
91 getDashboardFeatureProvider(context);
Fan Zhang917f1012018-02-21 15:22:25 -080092 // Load preference controllers from code
93 final List<AbstractPreferenceController> controllersFromCode =
Fan Zhangf7843ad2018-02-22 13:51:41 -080094 createPreferenceControllers(context);
Fan Zhang917f1012018-02-21 15:22:25 -080095 // Load preference controllers from xml definition
96 final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
97 .getPreferenceControllersFromXml(context, getPreferenceScreenResId());
98 // Filter xml-based controllers in case a similar controller is created from code already.
99 final List<BasePreferenceController> uniqueControllerFromXml =
100 PreferenceControllerListHelper.filterControllers(
101 controllersFromXml, controllersFromCode);
Fan Zhang66b573a2016-10-06 16:33:13 -0700102
Fan Zhang917f1012018-02-21 15:22:25 -0800103 // Add unique controllers to list.
104 if (controllersFromCode != null) {
hughchend49de612019-09-24 10:30:49 +0800105 mControllers.addAll(controllersFromCode);
Fan Zhang66b573a2016-10-06 16:33:13 -0700106 }
hughchend49de612019-09-24 10:30:49 +0800107 mControllers.addAll(uniqueControllerFromXml);
Fan Zhang917f1012018-02-21 15:22:25 -0800108
109 // And wire up with lifecycle.
tmfang27c84de2018-06-28 11:39:05 +0800110 final Lifecycle lifecycle = getSettingsLifecycle();
Jason Chiu1a1f9312019-10-01 17:35:00 +0800111 uniqueControllerFromXml.forEach(controller -> {
112 if (controller instanceof LifecycleObserver) {
113 lifecycle.addObserver((LifecycleObserver) controller);
114 }
115 });
Fan Zhang917f1012018-02-21 15:22:25 -0800116
Jason Chiu819abf72020-04-01 15:40:47 +0800117 // Set metrics category for BasePreferenceController.
118 final int metricCategory = getMetricsCategory();
119 mControllers.forEach(controller -> {
120 if (controller instanceof BasePreferenceController) {
121 ((BasePreferenceController) controller).setMetricsCategory(metricCategory);
122 }
123 });
124
Fan Zhang9dc9c612016-11-11 13:23:21 -0800125 mPlaceholderPreferenceController =
126 new DashboardTilePlaceholderPreferenceController(context);
hughchend49de612019-09-24 10:30:49 +0800127 mControllers.add(mPlaceholderPreferenceController);
128 for (AbstractPreferenceController controller : mControllers) {
Fan Zhang66b573a2016-10-06 16:33:13 -0700129 addPreferenceController(controller);
130 }
jackqdyulei22904a02018-12-27 14:40:09 -0800131 }
132
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800133 @VisibleForTesting
134 void checkUiBlocker(List<AbstractPreferenceController> controllers) {
jackqdyulei22904a02018-12-27 14:40:09 -0800135 final List<String> keys = new ArrayList<>();
Yi-Ling Chuang61022c62022-04-18 15:35:52 +0800136 final List<BasePreferenceController> baseControllers = new ArrayList<>();
Jason Chiu1a1f9312019-10-01 17:35:00 +0800137 controllers.forEach(controller -> {
138 if (controller instanceof BasePreferenceController.UiBlocker
139 && controller.isAvailable()) {
140 ((BasePreferenceController) controller).setUiBlockListener(this);
141 keys.add(controller.getPreferenceKey());
Yi-Ling Chuang61022c62022-04-18 15:35:52 +0800142 baseControllers.add((BasePreferenceController) controller);
Jason Chiu1a1f9312019-10-01 17:35:00 +0800143 }
144 });
jackqdyulei22904a02018-12-27 14:40:09 -0800145
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800146 if (!keys.isEmpty()) {
147 mBlockerController = new UiBlockerController(keys);
Yi-Ling Chuang61022c62022-04-18 15:35:52 +0800148 mBlockerController.start(() -> {
149 updatePreferenceVisibility(mPreferenceControllers);
150 baseControllers.forEach(controller -> controller.setUiBlockerFinished(true));
151 });
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800152 }
Fan Zhangbb6d2602016-10-04 13:21:06 -0700153 }
154
155 @Override
Fan Zhanga1a84e62016-10-19 14:15:34 -0700156 public void onCreate(Bundle icicle) {
157 super.onCreate(icicle);
158 // Set ComparisonCallback so we get better animation when list changes.
159 getPreferenceManager().setPreferenceComparisonCallback(
160 new PreferenceManager.SimplePreferenceComparisonCallback());
jeffreyhuangbf234af2017-11-14 15:09:12 -0800161 if (icicle != null) {
162 // Upon rotation configuration change we need to update preference states before any
163 // editing dialog is recreated (that would happen before onResume is called).
164 updatePreferenceStates();
165 }
Fan Zhanga1a84e62016-10-19 14:15:34 -0700166 }
167
168 @Override
Jason Chiu20df25e2020-09-30 14:08:33 +0800169 public void onCategoriesChanged(Set<String> categories) {
170 final String categoryKey = getCategoryKey();
171 final DashboardCategory dashboardCategory =
172 mDashboardFeatureProvider.getTilesForCategory(categoryKey);
173 if (dashboardCategory == null) {
Fan Zhang36924652016-10-07 08:38:48 -0700174 return;
175 }
Jason Chiu20df25e2020-09-30 14:08:33 +0800176
177 if (categories == null) {
178 // force refreshing
179 refreshDashboardTiles(getLogTag());
180 } else if (categories.contains(categoryKey)) {
181 Log.i(TAG, "refresh tiles for " + categoryKey);
182 refreshDashboardTiles(getLogTag());
183 }
Fan Zhang36924652016-10-07 08:38:48 -0700184 }
185
186 @Override
187 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
hughchend49de612019-09-24 10:30:49 +0800188 checkUiBlocker(mControllers);
Fan Zhang36924652016-10-07 08:38:48 -0700189 refreshAllPreferences(getLogTag());
Jason Chiub12e3b92020-04-07 16:30:18 +0800190 mControllers.stream()
191 .map(controller -> (Preference) findPreference(controller.getPreferenceKey()))
192 .filter(Objects::nonNull)
193 .forEach(preference -> {
194 // Give all controllers a chance to handle click.
195 preference.getExtras().putInt(CATEGORY, getMetricsCategory());
196 });
Fan Zhang36924652016-10-07 08:38:48 -0700197 }
198
199 @Override
Fan Zhangbb6d2602016-10-04 13:21:06 -0700200 public void onStart() {
201 super.onStart();
Fan Zhangfabbfb42016-10-07 12:41:43 -0700202 final DashboardCategory category =
203 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
Fan Zhang36924652016-10-07 08:38:48 -0700204 if (category == null) {
205 return;
206 }
Fan Zhangbb6d2602016-10-04 13:21:06 -0700207 final Activity activity = getActivity();
Jason Chiuc713c3e2021-06-17 17:48:02 +0800208 if (activity instanceof CategoryHandler) {
Fan Zhang36924652016-10-07 08:38:48 -0700209 mListeningToCategoryChange = true;
Jason Chiuc713c3e2021-06-17 17:48:02 +0800210 ((CategoryHandler) activity).getCategoryMixin().addCategoryListener(this);
Fan Zhangbb6d2602016-10-04 13:21:06 -0700211 }
Jason Chiubac59872019-10-24 17:30:34 +0800212 final ContentResolver resolver = getContentResolver();
213 mDashboardTilePrefKeys.values().stream()
214 .filter(Objects::nonNull)
215 .flatMap(List::stream)
216 .forEach(observer -> {
217 if (!mRegisteredObservers.contains(observer)) {
218 registerDynamicDataObserver(resolver, observer);
219 }
220 });
Fan Zhangbb6d2602016-10-04 13:21:06 -0700221 }
222
223 @Override
Fan Zhang66b573a2016-10-06 16:33:13 -0700224 public void onResume() {
225 super.onResume();
226 updatePreferenceStates();
Sunny Shaoe5e82402019-11-27 18:55:16 +0800227 writeElapsedTimeMetric(SettingsEnums.ACTION_DASHBOARD_VISIBLE_TIME,
Tsung-Mao Fang6af43b12022-03-04 17:52:36 +0800228 "isParalleledControllers:false");
Fan Zhang66b573a2016-10-06 16:33:13 -0700229 }
230
231 @Override
Fan Zhangbb6d2602016-10-04 13:21:06 -0700232 public boolean onPreferenceTreeClick(Preference preference) {
Jason Chiu1281e592019-12-12 17:31:28 +0800233 final Collection<List<AbstractPreferenceController>> controllers =
Ben Lin92751772017-12-21 17:32:34 -0800234 mPreferenceControllers.values();
Ben Lin92751772017-12-21 17:32:34 -0800235 for (List<AbstractPreferenceController> controllerList : controllers) {
236 for (AbstractPreferenceController controller : controllerList) {
237 if (controller.handlePreferenceTreeClick(preference)) {
Jason Chiu1281e592019-12-12 17:31:28 +0800238 // log here since calling super.onPreferenceTreeClick will be skipped
239 writePreferenceClickMetric(preference);
Ben Lin92751772017-12-21 17:32:34 -0800240 return true;
241 }
Fan Zhangbb6d2602016-10-04 13:21:06 -0700242 }
243 }
244 return super.onPreferenceTreeClick(preference);
245 }
246
247 @Override
248 public void onStop() {
249 super.onStop();
Jason Chiubac59872019-10-24 17:30:34 +0800250 unregisterDynamicDataObservers(new ArrayList<>(mRegisteredObservers));
Fan Zhang36924652016-10-07 08:38:48 -0700251 if (mListeningToCategoryChange) {
252 final Activity activity = getActivity();
Jason Chiuc713c3e2021-06-17 17:48:02 +0800253 if (activity instanceof CategoryHandler) {
254 ((CategoryHandler) activity).getCategoryMixin().removeCategoryListener(this);
Fan Zhang36924652016-10-07 08:38:48 -0700255 }
256 mListeningToCategoryChange = false;
Fan Zhangbb6d2602016-10-04 13:21:06 -0700257 }
258 }
259
Fan Zhang9679dba2017-10-31 14:27:18 -0700260 @Override
261 protected abstract int getPreferenceScreenResId();
262
Doris Ling8741c042018-08-27 14:11:55 -0700263 @Override
264 public void onExpandButtonClick() {
Fan Zhangff47b302018-11-09 14:52:42 -0800265 mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
Fan Zhang31b21002019-01-16 13:49:47 -0800266 SettingsEnums.ACTION_SETTINGS_ADVANCED_BUTTON_EXPAND,
Fan Zhangff47b302018-11-09 14:52:42 -0800267 getMetricsCategory(), null, 0);
Doris Ling8741c042018-08-27 14:11:55 -0700268 }
269
Fan Zhang7d5a9ee2018-08-09 17:32:37 -0700270 protected boolean shouldForceRoundedIcon() {
271 return false;
272 }
273
Fan Zhangf7843ad2018-02-22 13:51:41 -0800274 protected <T extends AbstractPreferenceController> T use(Class<T> clazz) {
Ben Lin92751772017-12-21 17:32:34 -0800275 List<AbstractPreferenceController> controllerList = mPreferenceControllers.get(clazz);
276 if (controllerList != null) {
277 if (controllerList.size() > 1) {
278 Log.w(TAG, "Multiple controllers of Class " + clazz.getSimpleName()
279 + " found, returning first one.");
280 }
281 return (T) controllerList.get(0);
282 }
283
284 return null;
Fan Zhangbb6d2602016-10-04 13:21:06 -0700285 }
286
Christian Göllnerff9065a2022-02-17 11:32:54 +0000287 /** Returns all controllers of type T. */
288 protected <T extends AbstractPreferenceController> List<T> useAll(Class<T> clazz) {
289 return (List<T>) mPreferenceControllers.getOrDefault(clazz, Collections.emptyList());
290 }
291
Tony Mantler1d583e12017-06-13 13:09:25 -0700292 protected void addPreferenceController(AbstractPreferenceController controller) {
Ben Lin92751772017-12-21 17:32:34 -0800293 if (mPreferenceControllers.get(controller.getClass()) == null) {
294 mPreferenceControllers.put(controller.getClass(), new ArrayList<>());
295 }
296 mPreferenceControllers.get(controller.getClass()).add(controller);
Fan Zhangbb6d2602016-10-04 13:21:06 -0700297 }
298
Fan Zhang36924652016-10-07 08:38:48 -0700299 /**
Fan Zhangfabbfb42016-10-07 12:41:43 -0700300 * Returns the CategoryKey for loading {@link DashboardCategory} for this fragment.
Fan Zhang36924652016-10-07 08:38:48 -0700301 */
Fan Zhang26a1def2017-07-26 10:58:32 -0700302 @VisibleForTesting
Fan Zhang7e6df832017-01-24 14:02:17 -0800303 public String getCategoryKey() {
304 return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
305 }
Fan Zhang36924652016-10-07 08:38:48 -0700306
307 /**
Fan Zhang66b573a2016-10-06 16:33:13 -0700308 * Get the tag string for logging.
309 */
310 protected abstract String getLogTag();
311
312 /**
Tony Mantler1d583e12017-06-13 13:09:25 -0700313 * Get a list of {@link AbstractPreferenceController} for this fragment.
Fan Zhang66b573a2016-10-06 16:33:13 -0700314 */
Fan Zhangf7843ad2018-02-22 13:51:41 -0800315 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
Fan Zhang917f1012018-02-21 15:22:25 -0800316 return null;
317 }
Fan Zhang66b573a2016-10-06 16:33:13 -0700318
319 /**
Doris Ling20d4b042016-11-22 16:37:06 -0800320 * Returns true if this tile should be displayed
321 */
Yanting Yang4e56cb22019-04-18 21:37:31 +0800322 @CallSuper
Doris Ling20d4b042016-11-22 16:37:06 -0800323 protected boolean displayTile(Tile tile) {
Yanting Yang4e56cb22019-04-18 21:37:31 +0800324 if (mSuppressInjectedTileKeys != null && tile.hasKey()) {
325 // For suppressing injected tiles for OEMs.
326 return !mSuppressInjectedTileKeys.contains(tile.getKey(getContext()));
327 }
Doris Ling20d4b042016-11-22 16:37:06 -0800328 return true;
329 }
330
331 /**
Fan Zhang36924652016-10-07 08:38:48 -0700332 * Displays resource based tiles.
333 */
Fan Zhang66b573a2016-10-06 16:33:13 -0700334 private void displayResourceTiles() {
335 final int resId = getPreferenceScreenResId();
336 if (resId <= 0) {
337 return;
338 }
339 addPreferencesFromResource(resId);
340 final PreferenceScreen screen = getPreferenceScreen();
Doris Ling8741c042018-08-27 14:11:55 -0700341 screen.setOnExpandButtonClickListener(this);
Bonian Chen048d9fe2020-04-20 12:33:49 +0800342 displayResourceTilesToScreen(screen);
343 }
344
345 /**
346 * Perform {@link AbstractPreferenceController#displayPreference(PreferenceScreen)}
347 * on all {@link AbstractPreferenceController}s.
348 */
349 protected void displayResourceTilesToScreen(PreferenceScreen screen) {
Ben Lin92751772017-12-21 17:32:34 -0800350 mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(
351 controller -> controller.displayPreference(screen));
Fan Zhang66b573a2016-10-06 16:33:13 -0700352 }
Fan Zhang36924652016-10-07 08:38:48 -0700353
354 /**
Bonian Chen1b9bda32020-02-04 10:27:50 +0800355 * Get current PreferenceController(s)
356 */
357 protected Collection<List<AbstractPreferenceController>> getPreferenceControllers() {
358 return mPreferenceControllers.values();
359 }
360
361 /**
Fan Zhang8b5bca52016-10-19 12:00:32 -0700362 * Update state of each preference managed by PreferenceController.
Fan Zhang36924652016-10-07 08:38:48 -0700363 */
Fan Zhangbeddff82016-11-18 11:10:05 -0800364 protected void updatePreferenceStates() {
Fan Zhang8b5bca52016-10-19 12:00:32 -0700365 final PreferenceScreen screen = getPreferenceScreen();
Ben Lin92751772017-12-21 17:32:34 -0800366 Collection<List<AbstractPreferenceController>> controllerLists =
367 mPreferenceControllers.values();
368 for (List<AbstractPreferenceController> controllerList : controllerLists) {
369 for (AbstractPreferenceController controller : controllerList) {
370 if (!controller.isAvailable()) {
371 continue;
372 }
Jason Chiua42a93b2018-12-18 18:27:50 +0800373
Ben Lin92751772017-12-21 17:32:34 -0800374 final String key = controller.getPreferenceKey();
Jason Chiua42a93b2018-12-18 18:27:50 +0800375 if (TextUtils.isEmpty(key)) {
376 Log.d(TAG, String.format("Preference key is %s in Controller %s",
377 key, controller.getClass().getSimpleName()));
378 continue;
379 }
Fan Zhang8b5bca52016-10-19 12:00:32 -0700380
Ben Lin92751772017-12-21 17:32:34 -0800381 final Preference preference = screen.findPreference(key);
382 if (preference == null) {
383 Log.d(TAG, String.format("Cannot find preference with key %s in Controller %s",
384 key, controller.getClass().getSimpleName()));
385 continue;
386 }
387 controller.updateState(preference);
Fan Zhang8b5bca52016-10-19 12:00:32 -0700388 }
Fan Zhang8b5bca52016-10-19 12:00:32 -0700389 }
390 }
391
Fan Zhang8b5bca52016-10-19 12:00:32 -0700392 /**
393 * Refresh all preference items, including both static prefs from xml, and dynamic items from
394 * DashboardCategory.
395 */
Jason Chiubac59872019-10-24 17:30:34 +0800396 private void refreshAllPreferences(final String tag) {
jackqdyulei22904a02018-12-27 14:40:09 -0800397 final PreferenceScreen screen = getPreferenceScreen();
Fan Zhang8b5bca52016-10-19 12:00:32 -0700398 // First remove old preferences.
jackqdyulei22904a02018-12-27 14:40:09 -0800399 if (screen != null) {
Fan Zhang8b5bca52016-10-19 12:00:32 -0700400 // Intentionally do not cache PreferenceScreen because it will be recreated later.
jackqdyulei22904a02018-12-27 14:40:09 -0800401 screen.removeAll();
Fan Zhang8b5bca52016-10-19 12:00:32 -0700402 }
403
404 // Add resource based tiles.
405 displayResourceTiles();
406
Jason Chiubac59872019-10-24 17:30:34 +0800407 refreshDashboardTiles(tag);
Fan Zhang293883b2018-10-26 14:53:12 -0700408
409 final Activity activity = getActivity();
410 if (activity != null) {
Jason Chiubac59872019-10-24 17:30:34 +0800411 Log.d(tag, "All preferences added, reporting fully drawn");
Fan Zhang293883b2018-10-26 14:53:12 -0700412 activity.reportFullyDrawn();
413 }
jackqdyulei22904a02018-12-27 14:40:09 -0800414
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800415 updatePreferenceVisibility(mPreferenceControllers);
jackqdyulei22904a02018-12-27 14:40:09 -0800416 }
417
Jason Chiuc8975552022-07-06 16:53:59 +0800418 /**
419 * Force update all the preferences in this fragment.
420 */
421 public void forceUpdatePreferences() {
422 final PreferenceScreen screen = getPreferenceScreen();
423 if (screen == null || mPreferenceControllers == null) {
424 return;
425 }
426 for (List<AbstractPreferenceController> controllerList : mPreferenceControllers.values()) {
427 for (AbstractPreferenceController controller : controllerList) {
428 final String key = controller.getPreferenceKey();
429 final Preference preference = findPreference(key);
430 if (preference == null) {
431 continue;
432 }
433 final boolean available = controller.isAvailable();
434 if (available) {
435 controller.updateState(preference);
436 }
437 preference.setVisible(available);
438 }
439 }
440 }
441
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800442 @VisibleForTesting
443 void updatePreferenceVisibility(
444 Map<Class, List<AbstractPreferenceController>> preferenceControllers) {
jackqdyulei22904a02018-12-27 14:40:09 -0800445 final PreferenceScreen screen = getPreferenceScreen();
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800446 if (screen == null || preferenceControllers == null || mBlockerController == null) {
jackqdyulei22904a02018-12-27 14:40:09 -0800447 return;
448 }
449
450 final boolean visible = mBlockerController.isBlockerFinished();
451 for (List<AbstractPreferenceController> controllerList :
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800452 preferenceControllers.values()) {
jackqdyulei22904a02018-12-27 14:40:09 -0800453 for (AbstractPreferenceController controller : controllerList) {
454 final String key = controller.getPreferenceKey();
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800455 final Preference preference = findPreference(key);
Yi-Ling Chuanga28770e2022-03-08 18:20:15 +0800456 if (preference == null) {
457 continue;
458 }
459 if (controller instanceof BasePreferenceController.UiBlocker) {
460 final boolean prefVisible =
461 ((BasePreferenceController) controller).getSavedPrefVisibility();
462 preference.setVisible(visible && controller.isAvailable() && prefVisible);
463 } else {
jackqdyulei22904a02018-12-27 14:40:09 -0800464 preference.setVisible(visible && controller.isAvailable());
465 }
466 }
467 }
Fan Zhang8b5bca52016-10-19 12:00:32 -0700468 }
469
470 /**
471 * Refresh preference items backed by DashboardCategory.
472 */
Jason Chiubac59872019-10-24 17:30:34 +0800473 private void refreshDashboardTiles(final String tag) {
Fan Zhang8b5bca52016-10-19 12:00:32 -0700474 final PreferenceScreen screen = getPreferenceScreen();
Fan Zhanga1a84e62016-10-19 14:15:34 -0700475
Fan Zhangfabbfb42016-10-07 12:41:43 -0700476 final DashboardCategory category =
477 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
Fan Zhang36924652016-10-07 08:38:48 -0700478 if (category == null) {
Jason Chiubac59872019-10-24 17:30:34 +0800479 Log.d(tag, "NO dashboard tiles for " + tag);
Fan Zhang36924652016-10-07 08:38:48 -0700480 return;
481 }
Doris Lingbcb76352017-11-22 17:29:21 -0800482 final List<Tile> tiles = category.getTiles();
Fan Zhange6c60c22016-10-04 17:48:32 -0700483 if (tiles == null) {
Jason Chiubac59872019-10-24 17:30:34 +0800484 Log.d(tag, "tile list is empty, skipping category " + category.key);
Fan Zhange6c60c22016-10-04 17:48:32 -0700485 return;
486 }
Fan Zhanga1a84e62016-10-19 14:15:34 -0700487 // Create a list to track which tiles are to be removed.
Jason Chiubac59872019-10-24 17:30:34 +0800488 final Map<String, List<DynamicDataObserver>> remove = new ArrayMap(mDashboardTilePrefKeys);
Fan Zhanga1a84e62016-10-19 14:15:34 -0700489
Jason Chiuc9615612022-05-06 11:14:54 +0800490 // Install dashboard tiles and collect pending observers.
Fan Zhang7d5a9ee2018-08-09 17:32:37 -0700491 final boolean forceRoundedIcons = shouldForceRoundedIcon();
Jason Chiuc9615612022-05-06 11:14:54 +0800492 final List<DynamicDataObserver> pendingObservers = new ArrayList<>();
Fan Zhangbb6d2602016-10-04 13:21:06 -0700493 for (Tile tile : tiles) {
494 final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
495 if (TextUtils.isEmpty(key)) {
Jason Chiubac59872019-10-24 17:30:34 +0800496 Log.d(tag, "tile does not contain a key, skipping " + tile);
Fan Zhangbb6d2602016-10-04 13:21:06 -0700497 continue;
498 }
Doris Ling20d4b042016-11-22 16:37:06 -0800499 if (!displayTile(tile)) {
500 continue;
501 }
Jason Chiuc9615612022-05-06 11:14:54 +0800502 final List<DynamicDataObserver> observers;
Jason Chiubac59872019-10-24 17:30:34 +0800503 if (mDashboardTilePrefKeys.containsKey(key)) {
Fan Zhanga1a84e62016-10-19 14:15:34 -0700504 // Have the key already, will rebind.
Doris Ling8b14a1a2017-08-17 14:45:12 -0700505 final Preference preference = screen.findPreference(key);
Jason Chiuc9615612022-05-06 11:14:54 +0800506 observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(
507 getActivity(), this, forceRoundedIcons, preference, tile, key,
Fan Zhang7d5a9ee2018-08-09 17:32:37 -0700508 mPlaceholderPreferenceController.getOrder());
Fan Zhanga1a84e62016-10-19 14:15:34 -0700509 } else {
510 // Don't have this key, add it.
Jason Chiubac59872019-10-24 17:30:34 +0800511 final Preference pref = createPreference(tile);
Jason Chiuc9615612022-05-06 11:14:54 +0800512 observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(
513 getActivity(), this, forceRoundedIcons, pref, tile, key,
514 mPlaceholderPreferenceController.getOrder());
Doris Ling8b14a1a2017-08-17 14:45:12 -0700515 screen.addPreference(pref);
Jason Chiubac59872019-10-24 17:30:34 +0800516 registerDynamicDataObservers(observers);
517 mDashboardTilePrefKeys.put(key, observers);
Fan Zhangbb6d2602016-10-04 13:21:06 -0700518 }
Jason Chiuc9615612022-05-06 11:14:54 +0800519 if (observers != null) {
520 pendingObservers.addAll(observers);
521 }
Fan Zhanga1a84e62016-10-19 14:15:34 -0700522 remove.remove(key);
Fan Zhangbb6d2602016-10-04 13:21:06 -0700523 }
Jason Chiuc9615612022-05-06 11:14:54 +0800524
525 // Remove tiles that are gone.
Jason Chiubac59872019-10-24 17:30:34 +0800526 for (Map.Entry<String, List<DynamicDataObserver>> entry : remove.entrySet()) {
527 final String key = entry.getKey();
Fan Zhanga1a84e62016-10-19 14:15:34 -0700528 mDashboardTilePrefKeys.remove(key);
Doris Ling8b14a1a2017-08-17 14:45:12 -0700529 final Preference preference = screen.findPreference(key);
530 if (preference != null) {
531 screen.removePreference(preference);
532 }
Jason Chiubac59872019-10-24 17:30:34 +0800533 unregisterDynamicDataObservers(entry.getValue());
Fan Zhanga1a84e62016-10-19 14:15:34 -0700534 }
Jason Chiuc9615612022-05-06 11:14:54 +0800535
536 // Wait for pending observers to update UI.
537 if (!pendingObservers.isEmpty()) {
538 final CountDownLatch mainLatch = new CountDownLatch(1);
539 new Thread(() -> {
540 pendingObservers.forEach(observer ->
541 awaitObserverLatch(observer.getCountDownLatch()));
542 mainLatch.countDown();
543 }).start();
544 Log.d(tag, "Start waiting observers");
545 awaitObserverLatch(mainLatch);
546 Log.d(tag, "Stop waiting observers");
547 pendingObservers.forEach(DynamicDataObserver::updateUi);
548 }
Fan Zhanga1a84e62016-10-19 14:15:34 -0700549 }
jackqdyulei22904a02018-12-27 14:40:09 -0800550
551 @Override
552 public void onBlockerWorkFinished(BasePreferenceController controller) {
553 mBlockerController.countDown(controller.getPreferenceKey());
Yi-Ling Chuanga28770e2022-03-08 18:20:15 +0800554 controller.setUiBlockerFinished(mBlockerController.isBlockerFinished());
jackqdyulei22904a02018-12-27 14:40:09 -0800555 }
Jason Chiubac59872019-10-24 17:30:34 +0800556
Jason Chiu90eef242021-11-19 14:35:27 +0800557 protected Preference createPreference(Tile tile) {
Jason Chiubac59872019-10-24 17:30:34 +0800558 return tile instanceof ProviderTile
559 ? new SwitchPreference(getPrefContext())
Jason Chiu10e675d2019-10-24 17:30:34 +0800560 : tile.hasSwitch()
govenliub70ddb92020-07-16 17:09:03 +0800561 ? new PrimarySwitchPreference(getPrefContext())
Jason Chiu10e675d2019-10-24 17:30:34 +0800562 : new Preference(getPrefContext());
Jason Chiubac59872019-10-24 17:30:34 +0800563 }
564
565 @VisibleForTesting
566 void registerDynamicDataObservers(List<DynamicDataObserver> observers) {
567 if (observers == null || observers.isEmpty()) {
568 return;
569 }
570 final ContentResolver resolver = getContentResolver();
571 observers.forEach(observer -> registerDynamicDataObserver(resolver, observer));
572 }
573
574 private void registerDynamicDataObserver(ContentResolver resolver,
575 DynamicDataObserver observer) {
576 Log.d(TAG, "register observer: @" + Integer.toHexString(observer.hashCode())
577 + ", uri: " + observer.getUri());
578 resolver.registerContentObserver(observer.getUri(), false, observer);
579 mRegisteredObservers.add(observer);
580 }
581
582 private void unregisterDynamicDataObservers(List<DynamicDataObserver> observers) {
583 if (observers == null || observers.isEmpty()) {
584 return;
585 }
586 final ContentResolver resolver = getContentResolver();
587 observers.forEach(observer -> {
588 Log.d(TAG, "unregister observer: @" + Integer.toHexString(observer.hashCode())
589 + ", uri: " + observer.getUri());
590 mRegisteredObservers.remove(observer);
591 resolver.unregisterContentObserver(observer);
592 });
593 }
Jason Chiuc9615612022-05-06 11:14:54 +0800594
595 private void awaitObserverLatch(CountDownLatch latch) {
596 try {
597 latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
598 } catch (InterruptedException e) {
599 // Do nothing
600 }
601 }
Fan Zhangbb6d2602016-10-04 13:21:06 -0700602}