blob: f8a5d76b6fab91e475d5f98387b157128463d863 [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;
jasonwshsud3b3ed52023-04-05 22:14:49 +080022import android.content.Intent;
Fan Zhang36924652016-10-07 08:38:48 -070023import android.os.Bundle;
jasonwshsud3b3ed52023-04-05 22:14:49 +080024import android.preference.PreferenceManager.OnActivityResultListener;
Fan Zhangbb6d2602016-10-04 13:21:06 -070025import android.text.TextUtils;
26import android.util.ArrayMap;
27import android.util.Log;
28
Yanting Yang4e56cb22019-04-18 21:37:31 +080029import androidx.annotation.CallSuper;
jasonwshsud3b3ed52023-04-05 22:14:49 +080030import androidx.annotation.Nullable;
Fan Zhang1c61a582018-07-26 11:26:11 -070031import androidx.annotation.VisibleForTesting;
Chaohui Wang70470582022-03-29 14:37:53 +080032import androidx.lifecycle.LifecycleObserver;
Fan Zhang1c61a582018-07-26 11:26:11 -070033import androidx.preference.Preference;
Peter Zhangbaeca6f2023-05-08 16:59:01 +020034import androidx.preference.PreferenceCategory;
Doris Ling8741c042018-08-27 14:11:55 -070035import androidx.preference.PreferenceGroup;
Fan Zhang1c61a582018-07-26 11:26:11 -070036import androidx.preference.PreferenceManager;
37import androidx.preference.PreferenceScreen;
Jason Chiubac59872019-10-24 17:30:34 +080038import androidx.preference.SwitchPreference;
Fan Zhang1c61a582018-07-26 11:26:11 -070039
Yanting Yang4e56cb22019-04-18 21:37:31 +080040import com.android.settings.R;
Fan Zhangbb6d2602016-10-04 13:21:06 -070041import com.android.settings.SettingsPreferenceFragment;
Fan Zhang917f1012018-02-21 15:22:25 -080042import com.android.settings.core.BasePreferenceController;
Jason Chiuc713c3e2021-06-17 17:48:02 +080043import com.android.settings.core.CategoryMixin.CategoryHandler;
44import com.android.settings.core.CategoryMixin.CategoryListener;
Fan Zhang917f1012018-02-21 15:22:25 -080045import com.android.settings.core.PreferenceControllerListHelper;
Fan Zhangbb6d2602016-10-04 13:21:06 -070046import com.android.settings.overlay.FeatureFactory;
Wilson Wu9edf7972021-08-27 10:19:56 +080047import com.android.settingslib.PrimarySwitchPreference;
Tony Mantler1d583e12017-06-13 13:09:25 -070048import com.android.settingslib.core.AbstractPreferenceController;
Fan Zhang917f1012018-02-21 15:22:25 -080049import com.android.settingslib.core.lifecycle.Lifecycle;
Fan Zhangbb6d2602016-10-04 13:21:06 -070050import com.android.settingslib.drawer.DashboardCategory;
Fan Zhangbb6d2602016-10-04 13:21:06 -070051import com.android.settingslib.drawer.Tile;
Raff Tsai6db277e2019-10-02 16:43:23 +080052import com.android.settingslib.search.Indexable;
Fan Zhangbb6d2602016-10-04 13:21:06 -070053
Fan Zhanga1a84e62016-10-19 14:15:34 -070054import java.util.ArrayList;
Yanting Yang4e56cb22019-04-18 21:37:31 +080055import java.util.Arrays;
Fan Zhangbb6d2602016-10-04 13:21:06 -070056import java.util.Collection;
Christian Göllnerff9065a2022-02-17 11:32:54 +000057import java.util.Collections;
Peter Zhangbaeca6f2023-05-08 16:59:01 +020058import java.util.Comparator;
Fan Zhangbb6d2602016-10-04 13:21:06 -070059import java.util.List;
60import java.util.Map;
Jason Chiubac59872019-10-24 17:30:34 +080061import java.util.Objects;
Jason Chiu20df25e2020-09-30 14:08:33 +080062import java.util.Set;
Jason Chiuc9615612022-05-06 11:14:54 +080063import java.util.concurrent.CountDownLatch;
64import java.util.concurrent.TimeUnit;
Fan Zhangbb6d2602016-10-04 13:21:06 -070065
66/**
67 * Base fragment for dashboard style UI containing a list of static and dynamic setting items.
68 */
69public abstract class DashboardFragment extends SettingsPreferenceFragment
Jason Chiuc713c3e2021-06-17 17:48:02 +080070 implements CategoryListener, Indexable, PreferenceGroup.OnExpandButtonClickListener,
jackqdyulei22904a02018-12-27 14:40:09 -080071 BasePreferenceController.UiBlockListener {
Raff Tsaif71db732019-12-02 10:55:29 +080072 public static final String CATEGORY = "category";
Fan Zhangdb1112a2016-10-18 12:58:31 -070073 private static final String TAG = "DashboardFragment";
Jason Chiuc9615612022-05-06 11:14:54 +080074 private static final long TIMEOUT_MILLIS = 50L;
Fan Zhangbb6d2602016-10-04 13:21:06 -070075
Jason Chiubac59872019-10-24 17:30:34 +080076 @VisibleForTesting
77 final ArrayMap<String, List<DynamicDataObserver>> mDashboardTilePrefKeys = new ArrayMap<>();
Ben Lin92751772017-12-21 17:32:34 -080078 private final Map<Class, List<AbstractPreferenceController>> mPreferenceControllers =
Fan Zhangbb6d2602016-10-04 13:21:06 -070079 new ArrayMap<>();
Jason Chiubac59872019-10-24 17:30:34 +080080 private final List<DynamicDataObserver> mRegisteredObservers = new ArrayList<>();
hughchend49de612019-09-24 10:30:49 +080081 private final List<AbstractPreferenceController> mControllers = new ArrayList<>();
Sunny Shaoe5e82402019-11-27 18:55:16 +080082 @VisibleForTesting
83 UiBlockerController mBlockerController;
Fan Zhangb297abc2018-02-21 09:37:10 -080084 private DashboardFeatureProvider mDashboardFeatureProvider;
Fan Zhang9dc9c612016-11-11 13:23:21 -080085 private DashboardTilePlaceholderPreferenceController mPlaceholderPreferenceController;
Fan Zhang36924652016-10-07 08:38:48 -070086 private boolean mListeningToCategoryChange;
Yanting Yang4e56cb22019-04-18 21:37:31 +080087 private List<String> mSuppressInjectedTileKeys;
Fan Zhangbb6d2602016-10-04 13:21:06 -070088
89 @Override
90 public void onAttach(Context context) {
91 super.onAttach(context);
Yanting Yang4e56cb22019-04-18 21:37:31 +080092 mSuppressInjectedTileKeys = Arrays.asList(context.getResources().getStringArray(
93 R.array.config_suppress_injected_tile_keys));
Fan Zhang917f1012018-02-21 15:22:25 -080094 mDashboardFeatureProvider = FeatureFactory.getFactory(context).
95 getDashboardFeatureProvider(context);
Fan Zhang917f1012018-02-21 15:22:25 -080096 // Load preference controllers from code
97 final List<AbstractPreferenceController> controllersFromCode =
Fan Zhangf7843ad2018-02-22 13:51:41 -080098 createPreferenceControllers(context);
Fan Zhang917f1012018-02-21 15:22:25 -080099 // Load preference controllers from xml definition
100 final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
101 .getPreferenceControllersFromXml(context, getPreferenceScreenResId());
102 // Filter xml-based controllers in case a similar controller is created from code already.
103 final List<BasePreferenceController> uniqueControllerFromXml =
104 PreferenceControllerListHelper.filterControllers(
105 controllersFromXml, controllersFromCode);
Fan Zhang66b573a2016-10-06 16:33:13 -0700106
Fan Zhang917f1012018-02-21 15:22:25 -0800107 // Add unique controllers to list.
108 if (controllersFromCode != null) {
hughchend49de612019-09-24 10:30:49 +0800109 mControllers.addAll(controllersFromCode);
Fan Zhang66b573a2016-10-06 16:33:13 -0700110 }
hughchend49de612019-09-24 10:30:49 +0800111 mControllers.addAll(uniqueControllerFromXml);
Fan Zhang917f1012018-02-21 15:22:25 -0800112
113 // And wire up with lifecycle.
tmfang27c84de2018-06-28 11:39:05 +0800114 final Lifecycle lifecycle = getSettingsLifecycle();
Jason Chiu1a1f9312019-10-01 17:35:00 +0800115 uniqueControllerFromXml.forEach(controller -> {
116 if (controller instanceof LifecycleObserver) {
117 lifecycle.addObserver((LifecycleObserver) controller);
118 }
119 });
Fan Zhang917f1012018-02-21 15:22:25 -0800120
Jason Chiu819abf72020-04-01 15:40:47 +0800121 // Set metrics category for BasePreferenceController.
122 final int metricCategory = getMetricsCategory();
123 mControllers.forEach(controller -> {
124 if (controller instanceof BasePreferenceController) {
125 ((BasePreferenceController) controller).setMetricsCategory(metricCategory);
126 }
127 });
128
Fan Zhang9dc9c612016-11-11 13:23:21 -0800129 mPlaceholderPreferenceController =
130 new DashboardTilePlaceholderPreferenceController(context);
hughchend49de612019-09-24 10:30:49 +0800131 mControllers.add(mPlaceholderPreferenceController);
132 for (AbstractPreferenceController controller : mControllers) {
Fan Zhang66b573a2016-10-06 16:33:13 -0700133 addPreferenceController(controller);
134 }
jackqdyulei22904a02018-12-27 14:40:09 -0800135 }
136
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800137 @VisibleForTesting
138 void checkUiBlocker(List<AbstractPreferenceController> controllers) {
jackqdyulei22904a02018-12-27 14:40:09 -0800139 final List<String> keys = new ArrayList<>();
Yi-Ling Chuang61022c62022-04-18 15:35:52 +0800140 final List<BasePreferenceController> baseControllers = new ArrayList<>();
Jason Chiu1a1f9312019-10-01 17:35:00 +0800141 controllers.forEach(controller -> {
142 if (controller instanceof BasePreferenceController.UiBlocker
143 && controller.isAvailable()) {
144 ((BasePreferenceController) controller).setUiBlockListener(this);
145 keys.add(controller.getPreferenceKey());
Yi-Ling Chuang61022c62022-04-18 15:35:52 +0800146 baseControllers.add((BasePreferenceController) controller);
Jason Chiu1a1f9312019-10-01 17:35:00 +0800147 }
148 });
jackqdyulei22904a02018-12-27 14:40:09 -0800149
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800150 if (!keys.isEmpty()) {
151 mBlockerController = new UiBlockerController(keys);
Yi-Ling Chuang61022c62022-04-18 15:35:52 +0800152 mBlockerController.start(() -> {
153 updatePreferenceVisibility(mPreferenceControllers);
154 baseControllers.forEach(controller -> controller.setUiBlockerFinished(true));
155 });
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800156 }
Fan Zhangbb6d2602016-10-04 13:21:06 -0700157 }
158
159 @Override
Fan Zhanga1a84e62016-10-19 14:15:34 -0700160 public void onCreate(Bundle icicle) {
161 super.onCreate(icicle);
162 // Set ComparisonCallback so we get better animation when list changes.
163 getPreferenceManager().setPreferenceComparisonCallback(
164 new PreferenceManager.SimplePreferenceComparisonCallback());
jeffreyhuangbf234af2017-11-14 15:09:12 -0800165 if (icicle != null) {
166 // Upon rotation configuration change we need to update preference states before any
167 // editing dialog is recreated (that would happen before onResume is called).
168 updatePreferenceStates();
169 }
Fan Zhanga1a84e62016-10-19 14:15:34 -0700170 }
171
172 @Override
Jason Chiu20df25e2020-09-30 14:08:33 +0800173 public void onCategoriesChanged(Set<String> categories) {
174 final String categoryKey = getCategoryKey();
175 final DashboardCategory dashboardCategory =
176 mDashboardFeatureProvider.getTilesForCategory(categoryKey);
177 if (dashboardCategory == null) {
Fan Zhang36924652016-10-07 08:38:48 -0700178 return;
179 }
Jason Chiu20df25e2020-09-30 14:08:33 +0800180
181 if (categories == null) {
182 // force refreshing
183 refreshDashboardTiles(getLogTag());
184 } else if (categories.contains(categoryKey)) {
185 Log.i(TAG, "refresh tiles for " + categoryKey);
186 refreshDashboardTiles(getLogTag());
187 }
Fan Zhang36924652016-10-07 08:38:48 -0700188 }
189
190 @Override
191 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
hughchend49de612019-09-24 10:30:49 +0800192 checkUiBlocker(mControllers);
Fan Zhang36924652016-10-07 08:38:48 -0700193 refreshAllPreferences(getLogTag());
Jason Chiub12e3b92020-04-07 16:30:18 +0800194 mControllers.stream()
195 .map(controller -> (Preference) findPreference(controller.getPreferenceKey()))
196 .filter(Objects::nonNull)
197 .forEach(preference -> {
198 // Give all controllers a chance to handle click.
199 preference.getExtras().putInt(CATEGORY, getMetricsCategory());
200 });
Fan Zhang36924652016-10-07 08:38:48 -0700201 }
202
203 @Override
Fan Zhangbb6d2602016-10-04 13:21:06 -0700204 public void onStart() {
205 super.onStart();
Fan Zhangfabbfb42016-10-07 12:41:43 -0700206 final DashboardCategory category =
207 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
Fan Zhang36924652016-10-07 08:38:48 -0700208 if (category == null) {
209 return;
210 }
Fan Zhangbb6d2602016-10-04 13:21:06 -0700211 final Activity activity = getActivity();
Jason Chiuc713c3e2021-06-17 17:48:02 +0800212 if (activity instanceof CategoryHandler) {
Fan Zhang36924652016-10-07 08:38:48 -0700213 mListeningToCategoryChange = true;
Jason Chiuc713c3e2021-06-17 17:48:02 +0800214 ((CategoryHandler) activity).getCategoryMixin().addCategoryListener(this);
Fan Zhangbb6d2602016-10-04 13:21:06 -0700215 }
Jason Chiubac59872019-10-24 17:30:34 +0800216 final ContentResolver resolver = getContentResolver();
217 mDashboardTilePrefKeys.values().stream()
218 .filter(Objects::nonNull)
219 .flatMap(List::stream)
220 .forEach(observer -> {
221 if (!mRegisteredObservers.contains(observer)) {
222 registerDynamicDataObserver(resolver, observer);
223 }
224 });
Fan Zhangbb6d2602016-10-04 13:21:06 -0700225 }
226
227 @Override
Fan Zhang66b573a2016-10-06 16:33:13 -0700228 public void onResume() {
229 super.onResume();
230 updatePreferenceStates();
231 }
232
233 @Override
Fan Zhangbb6d2602016-10-04 13:21:06 -0700234 public boolean onPreferenceTreeClick(Preference preference) {
Jason Chiu1281e592019-12-12 17:31:28 +0800235 final Collection<List<AbstractPreferenceController>> controllers =
Ben Lin92751772017-12-21 17:32:34 -0800236 mPreferenceControllers.values();
Ben Lin92751772017-12-21 17:32:34 -0800237 for (List<AbstractPreferenceController> controllerList : controllers) {
238 for (AbstractPreferenceController controller : controllerList) {
239 if (controller.handlePreferenceTreeClick(preference)) {
Jason Chiu1281e592019-12-12 17:31:28 +0800240 // log here since calling super.onPreferenceTreeClick will be skipped
241 writePreferenceClickMetric(preference);
Ben Lin92751772017-12-21 17:32:34 -0800242 return true;
243 }
Fan Zhangbb6d2602016-10-04 13:21:06 -0700244 }
245 }
246 return super.onPreferenceTreeClick(preference);
247 }
248
249 @Override
250 public void onStop() {
251 super.onStop();
Jason Chiubac59872019-10-24 17:30:34 +0800252 unregisterDynamicDataObservers(new ArrayList<>(mRegisteredObservers));
Fan Zhang36924652016-10-07 08:38:48 -0700253 if (mListeningToCategoryChange) {
254 final Activity activity = getActivity();
Jason Chiuc713c3e2021-06-17 17:48:02 +0800255 if (activity instanceof CategoryHandler) {
256 ((CategoryHandler) activity).getCategoryMixin().removeCategoryListener(this);
Fan Zhang36924652016-10-07 08:38:48 -0700257 }
258 mListeningToCategoryChange = false;
Fan Zhangbb6d2602016-10-04 13:21:06 -0700259 }
260 }
261
Fan Zhang9679dba2017-10-31 14:27:18 -0700262 @Override
263 protected abstract int getPreferenceScreenResId();
264
Doris Ling8741c042018-08-27 14:11:55 -0700265 @Override
266 public void onExpandButtonClick() {
Fan Zhangff47b302018-11-09 14:52:42 -0800267 mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
Fan Zhang31b21002019-01-16 13:49:47 -0800268 SettingsEnums.ACTION_SETTINGS_ADVANCED_BUTTON_EXPAND,
Fan Zhangff47b302018-11-09 14:52:42 -0800269 getMetricsCategory(), null, 0);
Doris Ling8741c042018-08-27 14:11:55 -0700270 }
271
jasonwshsud3b3ed52023-04-05 22:14:49 +0800272 @Override
273 public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
274 for (List<AbstractPreferenceController> controllerList : mPreferenceControllers.values()) {
275 for (AbstractPreferenceController controller : controllerList) {
276 if (controller instanceof OnActivityResultListener) {
277 ((OnActivityResultListener) controller).onActivityResult(
278 requestCode, resultCode, data);
279 }
280 }
281 }
282 super.onActivityResult(requestCode, resultCode, data);
283 }
284
Fan Zhang7d5a9ee2018-08-09 17:32:37 -0700285 protected boolean shouldForceRoundedIcon() {
286 return false;
287 }
288
Fan Zhangf7843ad2018-02-22 13:51:41 -0800289 protected <T extends AbstractPreferenceController> T use(Class<T> clazz) {
Ben Lin92751772017-12-21 17:32:34 -0800290 List<AbstractPreferenceController> controllerList = mPreferenceControllers.get(clazz);
291 if (controllerList != null) {
292 if (controllerList.size() > 1) {
293 Log.w(TAG, "Multiple controllers of Class " + clazz.getSimpleName()
294 + " found, returning first one.");
295 }
296 return (T) controllerList.get(0);
297 }
298
299 return null;
Fan Zhangbb6d2602016-10-04 13:21:06 -0700300 }
301
Christian Göllnerff9065a2022-02-17 11:32:54 +0000302 /** Returns all controllers of type T. */
303 protected <T extends AbstractPreferenceController> List<T> useAll(Class<T> clazz) {
304 return (List<T>) mPreferenceControllers.getOrDefault(clazz, Collections.emptyList());
305 }
306
Tony Mantler1d583e12017-06-13 13:09:25 -0700307 protected void addPreferenceController(AbstractPreferenceController controller) {
Ben Lin92751772017-12-21 17:32:34 -0800308 if (mPreferenceControllers.get(controller.getClass()) == null) {
309 mPreferenceControllers.put(controller.getClass(), new ArrayList<>());
310 }
311 mPreferenceControllers.get(controller.getClass()).add(controller);
Fan Zhangbb6d2602016-10-04 13:21:06 -0700312 }
313
Fan Zhang36924652016-10-07 08:38:48 -0700314 /**
Fan Zhangfabbfb42016-10-07 12:41:43 -0700315 * Returns the CategoryKey for loading {@link DashboardCategory} for this fragment.
Fan Zhang36924652016-10-07 08:38:48 -0700316 */
Fan Zhang26a1def2017-07-26 10:58:32 -0700317 @VisibleForTesting
Fan Zhang7e6df832017-01-24 14:02:17 -0800318 public String getCategoryKey() {
319 return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
320 }
Fan Zhang36924652016-10-07 08:38:48 -0700321
322 /**
Fan Zhang66b573a2016-10-06 16:33:13 -0700323 * Get the tag string for logging.
324 */
325 protected abstract String getLogTag();
326
327 /**
Tony Mantler1d583e12017-06-13 13:09:25 -0700328 * Get a list of {@link AbstractPreferenceController} for this fragment.
Fan Zhang66b573a2016-10-06 16:33:13 -0700329 */
Fan Zhangf7843ad2018-02-22 13:51:41 -0800330 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
Fan Zhang917f1012018-02-21 15:22:25 -0800331 return null;
332 }
Fan Zhang66b573a2016-10-06 16:33:13 -0700333
334 /**
Doris Ling20d4b042016-11-22 16:37:06 -0800335 * Returns true if this tile should be displayed
336 */
Yanting Yang4e56cb22019-04-18 21:37:31 +0800337 @CallSuper
Doris Ling20d4b042016-11-22 16:37:06 -0800338 protected boolean displayTile(Tile tile) {
Yanting Yang4e56cb22019-04-18 21:37:31 +0800339 if (mSuppressInjectedTileKeys != null && tile.hasKey()) {
340 // For suppressing injected tiles for OEMs.
341 return !mSuppressInjectedTileKeys.contains(tile.getKey(getContext()));
342 }
Doris Ling20d4b042016-11-22 16:37:06 -0800343 return true;
344 }
345
346 /**
Fan Zhang36924652016-10-07 08:38:48 -0700347 * Displays resource based tiles.
348 */
Fan Zhang66b573a2016-10-06 16:33:13 -0700349 private void displayResourceTiles() {
350 final int resId = getPreferenceScreenResId();
351 if (resId <= 0) {
352 return;
353 }
354 addPreferencesFromResource(resId);
355 final PreferenceScreen screen = getPreferenceScreen();
Doris Ling8741c042018-08-27 14:11:55 -0700356 screen.setOnExpandButtonClickListener(this);
Bonian Chen048d9fe2020-04-20 12:33:49 +0800357 displayResourceTilesToScreen(screen);
358 }
359
360 /**
361 * Perform {@link AbstractPreferenceController#displayPreference(PreferenceScreen)}
362 * on all {@link AbstractPreferenceController}s.
363 */
364 protected void displayResourceTilesToScreen(PreferenceScreen screen) {
Ben Lin92751772017-12-21 17:32:34 -0800365 mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(
366 controller -> controller.displayPreference(screen));
Fan Zhang66b573a2016-10-06 16:33:13 -0700367 }
Fan Zhang36924652016-10-07 08:38:48 -0700368
369 /**
Bonian Chen1b9bda32020-02-04 10:27:50 +0800370 * Get current PreferenceController(s)
371 */
372 protected Collection<List<AbstractPreferenceController>> getPreferenceControllers() {
373 return mPreferenceControllers.values();
374 }
375
376 /**
Fan Zhang8b5bca52016-10-19 12:00:32 -0700377 * Update state of each preference managed by PreferenceController.
Fan Zhang36924652016-10-07 08:38:48 -0700378 */
Fan Zhangbeddff82016-11-18 11:10:05 -0800379 protected void updatePreferenceStates() {
Fan Zhang8b5bca52016-10-19 12:00:32 -0700380 final PreferenceScreen screen = getPreferenceScreen();
Ben Lin92751772017-12-21 17:32:34 -0800381 Collection<List<AbstractPreferenceController>> controllerLists =
382 mPreferenceControllers.values();
383 for (List<AbstractPreferenceController> controllerList : controllerLists) {
384 for (AbstractPreferenceController controller : controllerList) {
385 if (!controller.isAvailable()) {
386 continue;
387 }
Jason Chiua42a93b2018-12-18 18:27:50 +0800388
Ben Lin92751772017-12-21 17:32:34 -0800389 final String key = controller.getPreferenceKey();
Jason Chiua42a93b2018-12-18 18:27:50 +0800390 if (TextUtils.isEmpty(key)) {
391 Log.d(TAG, String.format("Preference key is %s in Controller %s",
392 key, controller.getClass().getSimpleName()));
393 continue;
394 }
Fan Zhang8b5bca52016-10-19 12:00:32 -0700395
Ben Lin92751772017-12-21 17:32:34 -0800396 final Preference preference = screen.findPreference(key);
397 if (preference == null) {
398 Log.d(TAG, String.format("Cannot find preference with key %s in Controller %s",
399 key, controller.getClass().getSimpleName()));
400 continue;
401 }
402 controller.updateState(preference);
Fan Zhang8b5bca52016-10-19 12:00:32 -0700403 }
Fan Zhang8b5bca52016-10-19 12:00:32 -0700404 }
405 }
406
Fan Zhang8b5bca52016-10-19 12:00:32 -0700407 /**
408 * Refresh all preference items, including both static prefs from xml, and dynamic items from
409 * DashboardCategory.
410 */
Jason Chiubac59872019-10-24 17:30:34 +0800411 private void refreshAllPreferences(final String tag) {
jackqdyulei22904a02018-12-27 14:40:09 -0800412 final PreferenceScreen screen = getPreferenceScreen();
Fan Zhang8b5bca52016-10-19 12:00:32 -0700413 // First remove old preferences.
jackqdyulei22904a02018-12-27 14:40:09 -0800414 if (screen != null) {
Fan Zhang8b5bca52016-10-19 12:00:32 -0700415 // Intentionally do not cache PreferenceScreen because it will be recreated later.
jackqdyulei22904a02018-12-27 14:40:09 -0800416 screen.removeAll();
Fan Zhang8b5bca52016-10-19 12:00:32 -0700417 }
418
419 // Add resource based tiles.
420 displayResourceTiles();
421
Jason Chiubac59872019-10-24 17:30:34 +0800422 refreshDashboardTiles(tag);
Fan Zhang293883b2018-10-26 14:53:12 -0700423
424 final Activity activity = getActivity();
425 if (activity != null) {
Jason Chiubac59872019-10-24 17:30:34 +0800426 Log.d(tag, "All preferences added, reporting fully drawn");
Fan Zhang293883b2018-10-26 14:53:12 -0700427 activity.reportFullyDrawn();
428 }
jackqdyulei22904a02018-12-27 14:40:09 -0800429
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800430 updatePreferenceVisibility(mPreferenceControllers);
jackqdyulei22904a02018-12-27 14:40:09 -0800431 }
432
Jason Chiuc8975552022-07-06 16:53:59 +0800433 /**
434 * Force update all the preferences in this fragment.
435 */
436 public void forceUpdatePreferences() {
437 final PreferenceScreen screen = getPreferenceScreen();
438 if (screen == null || mPreferenceControllers == null) {
439 return;
440 }
441 for (List<AbstractPreferenceController> controllerList : mPreferenceControllers.values()) {
442 for (AbstractPreferenceController controller : controllerList) {
443 final String key = controller.getPreferenceKey();
444 final Preference preference = findPreference(key);
445 if (preference == null) {
446 continue;
447 }
448 final boolean available = controller.isAvailable();
449 if (available) {
450 controller.updateState(preference);
451 }
452 preference.setVisible(available);
453 }
454 }
455 }
456
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800457 @VisibleForTesting
458 void updatePreferenceVisibility(
459 Map<Class, List<AbstractPreferenceController>> preferenceControllers) {
jackqdyulei22904a02018-12-27 14:40:09 -0800460 final PreferenceScreen screen = getPreferenceScreen();
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800461 if (screen == null || preferenceControllers == null || mBlockerController == null) {
jackqdyulei22904a02018-12-27 14:40:09 -0800462 return;
463 }
464
465 final boolean visible = mBlockerController.isBlockerFinished();
466 for (List<AbstractPreferenceController> controllerList :
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800467 preferenceControllers.values()) {
jackqdyulei22904a02018-12-27 14:40:09 -0800468 for (AbstractPreferenceController controller : controllerList) {
469 final String key = controller.getPreferenceKey();
jackqdyuleia5f1b5c2019-01-14 10:55:39 -0800470 final Preference preference = findPreference(key);
Yi-Ling Chuanga28770e2022-03-08 18:20:15 +0800471 if (preference == null) {
472 continue;
473 }
474 if (controller instanceof BasePreferenceController.UiBlocker) {
475 final boolean prefVisible =
476 ((BasePreferenceController) controller).getSavedPrefVisibility();
477 preference.setVisible(visible && controller.isAvailable() && prefVisible);
478 } else {
jackqdyulei22904a02018-12-27 14:40:09 -0800479 preference.setVisible(visible && controller.isAvailable());
480 }
481 }
482 }
Fan Zhang8b5bca52016-10-19 12:00:32 -0700483 }
484
485 /**
486 * Refresh preference items backed by DashboardCategory.
487 */
Jason Chiubac59872019-10-24 17:30:34 +0800488 private void refreshDashboardTiles(final String tag) {
Fan Zhang8b5bca52016-10-19 12:00:32 -0700489 final PreferenceScreen screen = getPreferenceScreen();
Fan Zhanga1a84e62016-10-19 14:15:34 -0700490
Fan Zhangfabbfb42016-10-07 12:41:43 -0700491 final DashboardCategory category =
492 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
Fan Zhang36924652016-10-07 08:38:48 -0700493 if (category == null) {
Jason Chiubac59872019-10-24 17:30:34 +0800494 Log.d(tag, "NO dashboard tiles for " + tag);
Fan Zhang36924652016-10-07 08:38:48 -0700495 return;
496 }
Doris Lingbcb76352017-11-22 17:29:21 -0800497 final List<Tile> tiles = category.getTiles();
Fan Zhange6c60c22016-10-04 17:48:32 -0700498 if (tiles == null) {
Jason Chiubac59872019-10-24 17:30:34 +0800499 Log.d(tag, "tile list is empty, skipping category " + category.key);
Fan Zhange6c60c22016-10-04 17:48:32 -0700500 return;
501 }
Fan Zhanga1a84e62016-10-19 14:15:34 -0700502 // Create a list to track which tiles are to be removed.
Jason Chiubac59872019-10-24 17:30:34 +0800503 final Map<String, List<DynamicDataObserver>> remove = new ArrayMap(mDashboardTilePrefKeys);
Fan Zhanga1a84e62016-10-19 14:15:34 -0700504
Jason Chiuc9615612022-05-06 11:14:54 +0800505 // Install dashboard tiles and collect pending observers.
Fan Zhang7d5a9ee2018-08-09 17:32:37 -0700506 final boolean forceRoundedIcons = shouldForceRoundedIcon();
Jason Chiuc9615612022-05-06 11:14:54 +0800507 final List<DynamicDataObserver> pendingObservers = new ArrayList<>();
Peter Zhangbaeca6f2023-05-08 16:59:01 +0200508
509 // Move group tiles to the beginning of the list to ensure they are created before the
510 // other tiles.
511 tiles.sort(Comparator.comparingInt(tile -> tile.getType() == Tile.Type.GROUP ? 0 : 1));
Fan Zhangbb6d2602016-10-04 13:21:06 -0700512 for (Tile tile : tiles) {
513 final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
514 if (TextUtils.isEmpty(key)) {
Jason Chiubac59872019-10-24 17:30:34 +0800515 Log.d(tag, "tile does not contain a key, skipping " + tile);
Fan Zhangbb6d2602016-10-04 13:21:06 -0700516 continue;
517 }
Doris Ling20d4b042016-11-22 16:37:06 -0800518 if (!displayTile(tile)) {
519 continue;
520 }
Jason Chiuc9615612022-05-06 11:14:54 +0800521 final List<DynamicDataObserver> observers;
Jason Chiubac59872019-10-24 17:30:34 +0800522 if (mDashboardTilePrefKeys.containsKey(key)) {
Fan Zhanga1a84e62016-10-19 14:15:34 -0700523 // Have the key already, will rebind.
Doris Ling8b14a1a2017-08-17 14:45:12 -0700524 final Preference preference = screen.findPreference(key);
Jason Chiuc9615612022-05-06 11:14:54 +0800525 observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(
526 getActivity(), this, forceRoundedIcons, preference, tile, key,
Fan Zhang7d5a9ee2018-08-09 17:32:37 -0700527 mPlaceholderPreferenceController.getOrder());
Fan Zhanga1a84e62016-10-19 14:15:34 -0700528 } else {
529 // Don't have this key, add it.
Jason Chiubac59872019-10-24 17:30:34 +0800530 final Preference pref = createPreference(tile);
Jason Chiuc9615612022-05-06 11:14:54 +0800531 observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(
532 getActivity(), this, forceRoundedIcons, pref, tile, key,
533 mPlaceholderPreferenceController.getOrder());
Peter Zhangbaeca6f2023-05-08 16:59:01 +0200534 if (tile.hasGroupKey() && mDashboardTilePrefKeys.containsKey(tile.getGroupKey())) {
535 final Preference group = screen.findPreference(tile.getGroupKey());
536 if (group instanceof PreferenceCategory) {
537 ((PreferenceCategory) group).addPreference(pref);
538 }
539 } else {
540 screen.addPreference(pref);
541 }
Jason Chiubac59872019-10-24 17:30:34 +0800542 registerDynamicDataObservers(observers);
543 mDashboardTilePrefKeys.put(key, observers);
Fan Zhangbb6d2602016-10-04 13:21:06 -0700544 }
Jason Chiuc9615612022-05-06 11:14:54 +0800545 if (observers != null) {
546 pendingObservers.addAll(observers);
547 }
Fan Zhanga1a84e62016-10-19 14:15:34 -0700548 remove.remove(key);
Fan Zhangbb6d2602016-10-04 13:21:06 -0700549 }
Jason Chiuc9615612022-05-06 11:14:54 +0800550
551 // Remove tiles that are gone.
Jason Chiubac59872019-10-24 17:30:34 +0800552 for (Map.Entry<String, List<DynamicDataObserver>> entry : remove.entrySet()) {
553 final String key = entry.getKey();
Fan Zhanga1a84e62016-10-19 14:15:34 -0700554 mDashboardTilePrefKeys.remove(key);
Doris Ling8b14a1a2017-08-17 14:45:12 -0700555 final Preference preference = screen.findPreference(key);
556 if (preference != null) {
557 screen.removePreference(preference);
558 }
Jason Chiubac59872019-10-24 17:30:34 +0800559 unregisterDynamicDataObservers(entry.getValue());
Fan Zhanga1a84e62016-10-19 14:15:34 -0700560 }
Jason Chiuc9615612022-05-06 11:14:54 +0800561
562 // Wait for pending observers to update UI.
563 if (!pendingObservers.isEmpty()) {
564 final CountDownLatch mainLatch = new CountDownLatch(1);
565 new Thread(() -> {
566 pendingObservers.forEach(observer ->
567 awaitObserverLatch(observer.getCountDownLatch()));
568 mainLatch.countDown();
569 }).start();
570 Log.d(tag, "Start waiting observers");
571 awaitObserverLatch(mainLatch);
572 Log.d(tag, "Stop waiting observers");
573 pendingObservers.forEach(DynamicDataObserver::updateUi);
574 }
Fan Zhanga1a84e62016-10-19 14:15:34 -0700575 }
jackqdyulei22904a02018-12-27 14:40:09 -0800576
577 @Override
578 public void onBlockerWorkFinished(BasePreferenceController controller) {
579 mBlockerController.countDown(controller.getPreferenceKey());
Yi-Ling Chuanga28770e2022-03-08 18:20:15 +0800580 controller.setUiBlockerFinished(mBlockerController.isBlockerFinished());
jackqdyulei22904a02018-12-27 14:40:09 -0800581 }
Jason Chiubac59872019-10-24 17:30:34 +0800582
Jason Chiu90eef242021-11-19 14:35:27 +0800583 protected Preference createPreference(Tile tile) {
Peter Zhangbaeca6f2023-05-08 16:59:01 +0200584 switch (tile.getType()) {
585 case EXTERNAL_ACTION:
586 Preference externalActionPreference = new Preference(getPrefContext());
587 externalActionPreference
588 .setWidgetLayoutResource(R.layout.preference_external_action_icon);
589 return externalActionPreference;
590 case SWITCH:
591 return new SwitchPreference(getPrefContext());
592 case SWITCH_WITH_ACTION:
593 return new PrimarySwitchPreference(getPrefContext());
594 case GROUP:
Peter Zhangf6a267b2023-05-10 16:01:58 +0200595 mMetricsFeatureProvider.action(
596 mMetricsFeatureProvider.getAttribution(getActivity()),
597 SettingsEnums.ACTION_SETTINGS_GROUP_TILE_ADDED_TO_SCREEN,
598 getMetricsCategory(),
599 tile.getKey(getContext()),
600 /* value= */ 0);
Peter Zhangbaeca6f2023-05-08 16:59:01 +0200601 return new PreferenceCategory((getPrefContext()));
602 case ACTION:
603 default:
604 return new Preference(getPrefContext());
Peter Zhangddb65e52023-05-07 23:55:50 +0200605 }
Jason Chiubac59872019-10-24 17:30:34 +0800606 }
607
608 @VisibleForTesting
609 void registerDynamicDataObservers(List<DynamicDataObserver> observers) {
610 if (observers == null || observers.isEmpty()) {
611 return;
612 }
613 final ContentResolver resolver = getContentResolver();
614 observers.forEach(observer -> registerDynamicDataObserver(resolver, observer));
615 }
616
617 private void registerDynamicDataObserver(ContentResolver resolver,
618 DynamicDataObserver observer) {
619 Log.d(TAG, "register observer: @" + Integer.toHexString(observer.hashCode())
620 + ", uri: " + observer.getUri());
621 resolver.registerContentObserver(observer.getUri(), false, observer);
622 mRegisteredObservers.add(observer);
623 }
624
625 private void unregisterDynamicDataObservers(List<DynamicDataObserver> observers) {
626 if (observers == null || observers.isEmpty()) {
627 return;
628 }
629 final ContentResolver resolver = getContentResolver();
630 observers.forEach(observer -> {
631 Log.d(TAG, "unregister observer: @" + Integer.toHexString(observer.hashCode())
632 + ", uri: " + observer.getUri());
633 mRegisteredObservers.remove(observer);
634 resolver.unregisterContentObserver(observer);
635 });
636 }
Jason Chiuc9615612022-05-06 11:14:54 +0800637
638 private void awaitObserverLatch(CountDownLatch latch) {
639 try {
640 latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
641 } catch (InterruptedException e) {
642 // Do nothing
643 }
644 }
Fan Zhangbb6d2602016-10-04 13:21:06 -0700645}