Loading src/com/android/settings/core/CategoryMixin.java +7 −0 Original line number Diff line number Diff line Loading @@ -58,6 +58,7 @@ public class CategoryMixin implements LifecycleObserver { private final PackageReceiver mPackageReceiver = new PackageReceiver(); private final List<CategoryListener> mCategoryListeners = new ArrayList<>(); private int mCategoriesUpdateTaskCount; private boolean mFirstOnResume = true; public CategoryMixin(Context context) { mContext = context; Loading @@ -75,6 +76,12 @@ public class CategoryMixin implements LifecycleObserver { filter.addDataScheme(DATA_SCHEME_PKG); mContext.registerReceiver(mPackageReceiver, filter); if (mFirstOnResume) { // Skip since all tiles have been refreshed in DashboardFragment.onCreatePreferences(). Log.d(TAG, "Skip categories update"); mFirstOnResume = false; return; } updateCategories(); } Loading src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java +9 −12 Original line number Diff line number Diff line Loading @@ -235,13 +235,13 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { public void onDataChanged() { switch (method) { case METHOD_GET_DYNAMIC_TITLE: refreshTitle(uri, pref); refreshTitle(uri, pref, this); break; case METHOD_GET_DYNAMIC_SUMMARY: refreshSummary(uri, pref); refreshSummary(uri, pref, this); break; case METHOD_IS_CHECKED: refreshSwitch(uri, pref); refreshSwitch(uri, pref, this); break; } } Loading @@ -262,19 +262,18 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_TITLE_URI, METHOD_GET_DYNAMIC_TITLE); refreshTitle(uri, preference); return createDynamicDataObserver(METHOD_GET_DYNAMIC_TITLE, uri, preference); } return null; } private void refreshTitle(Uri uri, Preference preference) { private void refreshTitle(Uri uri, Preference preference, DynamicDataObserver observer) { ThreadUtils.postOnBackgroundThread(() -> { final Map<String, IContentProvider> providerMap = new ArrayMap<>(); final String titleFromUri = TileUtils.getTextFromUri( mContext, uri, providerMap, META_DATA_PREFERENCE_TITLE); if (!TextUtils.equals(titleFromUri, preference.getTitle())) { ThreadUtils.postOnMainThread(() -> preference.setTitle(titleFromUri)); observer.post(() -> preference.setTitle(titleFromUri)); } }); } Loading @@ -291,19 +290,18 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SUMMARY_URI, METHOD_GET_DYNAMIC_SUMMARY); refreshSummary(uri, preference); return createDynamicDataObserver(METHOD_GET_DYNAMIC_SUMMARY, uri, preference); } return null; } private void refreshSummary(Uri uri, Preference preference) { private void refreshSummary(Uri uri, Preference preference, DynamicDataObserver observer) { ThreadUtils.postOnBackgroundThread(() -> { final Map<String, IContentProvider> providerMap = new ArrayMap<>(); final String summaryFromUri = TileUtils.getTextFromUri( mContext, uri, providerMap, META_DATA_PREFERENCE_SUMMARY); if (!TextUtils.equals(summaryFromUri, preference.getSummary())) { ThreadUtils.postOnMainThread(() -> preference.setSummary(summaryFromUri)); observer.post(() -> preference.setSummary(summaryFromUri)); } }); } Loading @@ -323,7 +321,6 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { final Uri isCheckedUri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SWITCH_URI, METHOD_IS_CHECKED); setSwitchEnabled(preference, false); refreshSwitch(isCheckedUri, preference); return createDynamicDataObserver(METHOD_IS_CHECKED, isCheckedUri, preference); } Loading @@ -350,12 +347,12 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { }); } private void refreshSwitch(Uri uri, Preference preference) { private void refreshSwitch(Uri uri, Preference preference, DynamicDataObserver observer) { ThreadUtils.postOnBackgroundThread(() -> { final Map<String, IContentProvider> providerMap = new ArrayMap<>(); final boolean checked = TileUtils.getBooleanFromUri(mContext, uri, providerMap, EXTRA_SWITCH_CHECKED_STATE); ThreadUtils.postOnMainThread(() -> { observer.post(() -> { setSwitchChecked(preference, checked); setSwitchEnabled(preference, true); }); Loading src/com/android/settings/dashboard/DashboardFragment.java +38 −9 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package com.android.settings.dashboard; import android.app.Activity; import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; import android.content.ContentResolver; import android.content.Context; Loading Loading @@ -57,6 +56,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Base fragment for dashboard style UI containing a list of static and dynamic setting items. Loading @@ -66,6 +67,7 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment BasePreferenceController.UiBlockListener { public static final String CATEGORY = "category"; private static final String TAG = "DashboardFragment"; private static final long TIMEOUT_MILLIS = 50L; @VisibleForTesting final ArrayMap<String, List<DynamicDataObserver>> mDashboardTilePrefKeys = new ArrayMap<>(); Loading Loading @@ -461,8 +463,9 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment // Create a list to track which tiles are to be removed. final Map<String, List<DynamicDataObserver>> remove = new ArrayMap(mDashboardTilePrefKeys); // Install dashboard tiles. // Install dashboard tiles and collect pending observers. final boolean forceRoundedIcons = shouldForceRoundedIcon(); final List<DynamicDataObserver> pendingObservers = new ArrayList<>(); for (Tile tile : tiles) { final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile); if (TextUtils.isEmpty(key)) { Loading @@ -472,26 +475,30 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment if (!displayTile(tile)) { continue; } final List<DynamicDataObserver> observers; if (mDashboardTilePrefKeys.containsKey(key)) { // Have the key already, will rebind. final Preference preference = screen.findPreference(key); mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(), this, forceRoundedIcons, preference, tile, key, observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers( getActivity(), this, forceRoundedIcons, preference, tile, key, mPlaceholderPreferenceController.getOrder()); } else { // Don't have this key, add it. final Preference pref = createPreference(tile); final List<DynamicDataObserver> observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(), this, forceRoundedIcons, pref, tile, key, observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers( getActivity(), this, forceRoundedIcons, pref, tile, key, mPlaceholderPreferenceController.getOrder()); screen.addPreference(pref); registerDynamicDataObservers(observers); mDashboardTilePrefKeys.put(key, observers); } if (observers != null) { pendingObservers.addAll(observers); } remove.remove(key); } // Finally remove tiles that are gone. // Remove tiles that are gone. for (Map.Entry<String, List<DynamicDataObserver>> entry : remove.entrySet()) { final String key = entry.getKey(); mDashboardTilePrefKeys.remove(key); Loading @@ -501,6 +508,20 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment } unregisterDynamicDataObservers(entry.getValue()); } // Wait for pending observers to update UI. if (!pendingObservers.isEmpty()) { final CountDownLatch mainLatch = new CountDownLatch(1); new Thread(() -> { pendingObservers.forEach(observer -> awaitObserverLatch(observer.getCountDownLatch())); mainLatch.countDown(); }).start(); Log.d(tag, "Start waiting observers"); awaitObserverLatch(mainLatch); Log.d(tag, "Stop waiting observers"); pendingObservers.forEach(DynamicDataObserver::updateUi); } } @Override Loading Loading @@ -546,4 +567,12 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment resolver.unregisterContentObserver(observer); }); } private void awaitObserverLatch(CountDownLatch latch) { try { latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // Do nothing } } } src/com/android/settings/dashboard/DynamicDataObserver.java +33 −0 Original line number Diff line number Diff line Loading @@ -20,13 +20,24 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; import com.android.settingslib.utils.ThreadUtils; import java.util.concurrent.CountDownLatch; /** * Observer for updating injected dynamic data. */ public abstract class DynamicDataObserver extends ContentObserver { private Runnable mUpdateRunnable; private CountDownLatch mCountDownLatch; private boolean mUpdateDelegated; protected DynamicDataObserver() { super(new Handler(Looper.getMainLooper())); mCountDownLatch = new CountDownLatch(1); // Load data for the first time onDataChanged(); } /** Returns the uri of the callback. */ Loading @@ -35,8 +46,30 @@ public abstract class DynamicDataObserver extends ContentObserver { /** Called when data changes. */ public abstract void onDataChanged(); /** Calls the runnable to update UI */ public synchronized void updateUi() { mUpdateDelegated = true; if (mUpdateRunnable != null) { mUpdateRunnable.run(); } } /** Returns the count-down latch */ public CountDownLatch getCountDownLatch() { return mCountDownLatch; } @Override public void onChange(boolean selfChange) { onDataChanged(); } protected synchronized void post(Runnable runnable) { if (mUpdateDelegated) { ThreadUtils.postOnMainThread(runnable); } else { mUpdateRunnable = runnable; mCountDownLatch.countDown(); } } } src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java +1 −1 Original line number Diff line number Diff line Loading @@ -176,7 +176,7 @@ public class BluetoothDevicesSlice implements CustomSliceable { List<CachedBluetoothDevice> getPairedBluetoothDevices() { final List<CachedBluetoothDevice> bluetoothDeviceList = new ArrayList<>(); // If Bluetooth is disable, skip getting the Bluetooth devices. // If Bluetooth is disabled, skip getting the Bluetooth devices. if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) { Log.i(TAG, "Cannot get Bluetooth devices, Bluetooth is disabled."); return bluetoothDeviceList; Loading Loading
src/com/android/settings/core/CategoryMixin.java +7 −0 Original line number Diff line number Diff line Loading @@ -58,6 +58,7 @@ public class CategoryMixin implements LifecycleObserver { private final PackageReceiver mPackageReceiver = new PackageReceiver(); private final List<CategoryListener> mCategoryListeners = new ArrayList<>(); private int mCategoriesUpdateTaskCount; private boolean mFirstOnResume = true; public CategoryMixin(Context context) { mContext = context; Loading @@ -75,6 +76,12 @@ public class CategoryMixin implements LifecycleObserver { filter.addDataScheme(DATA_SCHEME_PKG); mContext.registerReceiver(mPackageReceiver, filter); if (mFirstOnResume) { // Skip since all tiles have been refreshed in DashboardFragment.onCreatePreferences(). Log.d(TAG, "Skip categories update"); mFirstOnResume = false; return; } updateCategories(); } Loading
src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java +9 −12 Original line number Diff line number Diff line Loading @@ -235,13 +235,13 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { public void onDataChanged() { switch (method) { case METHOD_GET_DYNAMIC_TITLE: refreshTitle(uri, pref); refreshTitle(uri, pref, this); break; case METHOD_GET_DYNAMIC_SUMMARY: refreshSummary(uri, pref); refreshSummary(uri, pref, this); break; case METHOD_IS_CHECKED: refreshSwitch(uri, pref); refreshSwitch(uri, pref, this); break; } } Loading @@ -262,19 +262,18 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_TITLE_URI, METHOD_GET_DYNAMIC_TITLE); refreshTitle(uri, preference); return createDynamicDataObserver(METHOD_GET_DYNAMIC_TITLE, uri, preference); } return null; } private void refreshTitle(Uri uri, Preference preference) { private void refreshTitle(Uri uri, Preference preference, DynamicDataObserver observer) { ThreadUtils.postOnBackgroundThread(() -> { final Map<String, IContentProvider> providerMap = new ArrayMap<>(); final String titleFromUri = TileUtils.getTextFromUri( mContext, uri, providerMap, META_DATA_PREFERENCE_TITLE); if (!TextUtils.equals(titleFromUri, preference.getTitle())) { ThreadUtils.postOnMainThread(() -> preference.setTitle(titleFromUri)); observer.post(() -> preference.setTitle(titleFromUri)); } }); } Loading @@ -291,19 +290,18 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SUMMARY_URI, METHOD_GET_DYNAMIC_SUMMARY); refreshSummary(uri, preference); return createDynamicDataObserver(METHOD_GET_DYNAMIC_SUMMARY, uri, preference); } return null; } private void refreshSummary(Uri uri, Preference preference) { private void refreshSummary(Uri uri, Preference preference, DynamicDataObserver observer) { ThreadUtils.postOnBackgroundThread(() -> { final Map<String, IContentProvider> providerMap = new ArrayMap<>(); final String summaryFromUri = TileUtils.getTextFromUri( mContext, uri, providerMap, META_DATA_PREFERENCE_SUMMARY); if (!TextUtils.equals(summaryFromUri, preference.getSummary())) { ThreadUtils.postOnMainThread(() -> preference.setSummary(summaryFromUri)); observer.post(() -> preference.setSummary(summaryFromUri)); } }); } Loading @@ -323,7 +321,6 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { final Uri isCheckedUri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SWITCH_URI, METHOD_IS_CHECKED); setSwitchEnabled(preference, false); refreshSwitch(isCheckedUri, preference); return createDynamicDataObserver(METHOD_IS_CHECKED, isCheckedUri, preference); } Loading @@ -350,12 +347,12 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { }); } private void refreshSwitch(Uri uri, Preference preference) { private void refreshSwitch(Uri uri, Preference preference, DynamicDataObserver observer) { ThreadUtils.postOnBackgroundThread(() -> { final Map<String, IContentProvider> providerMap = new ArrayMap<>(); final boolean checked = TileUtils.getBooleanFromUri(mContext, uri, providerMap, EXTRA_SWITCH_CHECKED_STATE); ThreadUtils.postOnMainThread(() -> { observer.post(() -> { setSwitchChecked(preference, checked); setSwitchEnabled(preference, true); }); Loading
src/com/android/settings/dashboard/DashboardFragment.java +38 −9 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package com.android.settings.dashboard; import android.app.Activity; import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; import android.content.ContentResolver; import android.content.Context; Loading Loading @@ -57,6 +56,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Base fragment for dashboard style UI containing a list of static and dynamic setting items. Loading @@ -66,6 +67,7 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment BasePreferenceController.UiBlockListener { public static final String CATEGORY = "category"; private static final String TAG = "DashboardFragment"; private static final long TIMEOUT_MILLIS = 50L; @VisibleForTesting final ArrayMap<String, List<DynamicDataObserver>> mDashboardTilePrefKeys = new ArrayMap<>(); Loading Loading @@ -461,8 +463,9 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment // Create a list to track which tiles are to be removed. final Map<String, List<DynamicDataObserver>> remove = new ArrayMap(mDashboardTilePrefKeys); // Install dashboard tiles. // Install dashboard tiles and collect pending observers. final boolean forceRoundedIcons = shouldForceRoundedIcon(); final List<DynamicDataObserver> pendingObservers = new ArrayList<>(); for (Tile tile : tiles) { final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile); if (TextUtils.isEmpty(key)) { Loading @@ -472,26 +475,30 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment if (!displayTile(tile)) { continue; } final List<DynamicDataObserver> observers; if (mDashboardTilePrefKeys.containsKey(key)) { // Have the key already, will rebind. final Preference preference = screen.findPreference(key); mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(), this, forceRoundedIcons, preference, tile, key, observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers( getActivity(), this, forceRoundedIcons, preference, tile, key, mPlaceholderPreferenceController.getOrder()); } else { // Don't have this key, add it. final Preference pref = createPreference(tile); final List<DynamicDataObserver> observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(), this, forceRoundedIcons, pref, tile, key, observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers( getActivity(), this, forceRoundedIcons, pref, tile, key, mPlaceholderPreferenceController.getOrder()); screen.addPreference(pref); registerDynamicDataObservers(observers); mDashboardTilePrefKeys.put(key, observers); } if (observers != null) { pendingObservers.addAll(observers); } remove.remove(key); } // Finally remove tiles that are gone. // Remove tiles that are gone. for (Map.Entry<String, List<DynamicDataObserver>> entry : remove.entrySet()) { final String key = entry.getKey(); mDashboardTilePrefKeys.remove(key); Loading @@ -501,6 +508,20 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment } unregisterDynamicDataObservers(entry.getValue()); } // Wait for pending observers to update UI. if (!pendingObservers.isEmpty()) { final CountDownLatch mainLatch = new CountDownLatch(1); new Thread(() -> { pendingObservers.forEach(observer -> awaitObserverLatch(observer.getCountDownLatch())); mainLatch.countDown(); }).start(); Log.d(tag, "Start waiting observers"); awaitObserverLatch(mainLatch); Log.d(tag, "Stop waiting observers"); pendingObservers.forEach(DynamicDataObserver::updateUi); } } @Override Loading Loading @@ -546,4 +567,12 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment resolver.unregisterContentObserver(observer); }); } private void awaitObserverLatch(CountDownLatch latch) { try { latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // Do nothing } } }
src/com/android/settings/dashboard/DynamicDataObserver.java +33 −0 Original line number Diff line number Diff line Loading @@ -20,13 +20,24 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; import com.android.settingslib.utils.ThreadUtils; import java.util.concurrent.CountDownLatch; /** * Observer for updating injected dynamic data. */ public abstract class DynamicDataObserver extends ContentObserver { private Runnable mUpdateRunnable; private CountDownLatch mCountDownLatch; private boolean mUpdateDelegated; protected DynamicDataObserver() { super(new Handler(Looper.getMainLooper())); mCountDownLatch = new CountDownLatch(1); // Load data for the first time onDataChanged(); } /** Returns the uri of the callback. */ Loading @@ -35,8 +46,30 @@ public abstract class DynamicDataObserver extends ContentObserver { /** Called when data changes. */ public abstract void onDataChanged(); /** Calls the runnable to update UI */ public synchronized void updateUi() { mUpdateDelegated = true; if (mUpdateRunnable != null) { mUpdateRunnable.run(); } } /** Returns the count-down latch */ public CountDownLatch getCountDownLatch() { return mCountDownLatch; } @Override public void onChange(boolean selfChange) { onDataChanged(); } protected synchronized void post(Runnable runnable) { if (mUpdateDelegated) { ThreadUtils.postOnMainThread(runnable); } else { mUpdateRunnable = runnable; mCountDownLatch.countDown(); } } }
src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java +1 −1 Original line number Diff line number Diff line Loading @@ -176,7 +176,7 @@ public class BluetoothDevicesSlice implements CustomSliceable { List<CachedBluetoothDevice> getPairedBluetoothDevices() { final List<CachedBluetoothDevice> bluetoothDeviceList = new ArrayList<>(); // If Bluetooth is disable, skip getting the Bluetooth devices. // If Bluetooth is disabled, skip getting the Bluetooth devices. if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) { Log.i(TAG, "Cannot get Bluetooth devices, Bluetooth is disabled."); return bluetoothDeviceList; Loading