Merge "Reduce flickers of Injection" into tm-dev
This commit is contained in:
@@ -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;
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
@@ -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;
|
||||
@@ -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.
|
||||
@@ -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<>();
|
||||
@@ -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)) {
|
||||
@@ -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,
|
||||
mPlaceholderPreferenceController.getOrder());
|
||||
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);
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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. */
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -308,8 +308,12 @@ public class DashboardFeatureProviderImplTest {
|
||||
mActivity, mFragment, mForceRoundedIcon, preference, tile, null /* key */,
|
||||
Preference.DEFAULT_ORDER);
|
||||
|
||||
assertThat(preference.getSummary()).isEqualTo(ShadowTileUtils.MOCK_SUMMARY);
|
||||
assertThat(observers.get(0).getUri().toString()).isEqualTo(uriString);
|
||||
assertThat(preference.getSummary()).isNotEqualTo(ShadowTileUtils.MOCK_TEXT);
|
||||
|
||||
observers.get(0).updateUi();
|
||||
|
||||
assertThat(preference.getSummary()).isEqualTo(ShadowTileUtils.MOCK_TEXT);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -324,8 +328,12 @@ public class DashboardFeatureProviderImplTest {
|
||||
mActivity, mFragment, mForceRoundedIcon, preference, tile, null /* key */,
|
||||
Preference.DEFAULT_ORDER);
|
||||
|
||||
assertThat(preference.getTitle()).isEqualTo(ShadowTileUtils.MOCK_SUMMARY);
|
||||
assertThat(observers.get(0).getUri().toString()).isEqualTo(uriString);
|
||||
assertThat(preference.getTitle()).isNotEqualTo(ShadowTileUtils.MOCK_TEXT);
|
||||
|
||||
observers.get(0).updateUi();
|
||||
|
||||
assertThat(preference.getTitle()).isEqualTo(ShadowTileUtils.MOCK_TEXT);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -379,6 +387,7 @@ public class DashboardFeatureProviderImplTest {
|
||||
final List<DynamicDataObserver> observers = mImpl.bindPreferenceToTileAndGetObservers(
|
||||
mActivity, mFragment, mForceRoundedIcon, preference, tile, null /* key */,
|
||||
Preference.DEFAULT_ORDER);
|
||||
observers.get(0).updateUi();
|
||||
|
||||
ShadowTileUtils.setProviderChecked(false);
|
||||
observers.get(0).onDataChanged();
|
||||
|
@@ -34,7 +34,7 @@ import java.util.Map;
|
||||
@Implements(TileUtils.class)
|
||||
public class ShadowTileUtils {
|
||||
|
||||
public static final String MOCK_SUMMARY = "summary";
|
||||
public static final String MOCK_TEXT = "text";
|
||||
|
||||
private static boolean sChecked;
|
||||
private static Bundle sResult;
|
||||
@@ -42,13 +42,14 @@ public class ShadowTileUtils {
|
||||
@Implementation
|
||||
protected static String getTextFromUri(Context context, Uri uri,
|
||||
Map<String, IContentProvider> providerMap, String key) {
|
||||
return MOCK_SUMMARY;
|
||||
return MOCK_TEXT;
|
||||
}
|
||||
|
||||
@Implementation
|
||||
protected static Pair<String, Integer> getIconFromUri(Context context, String packageName,
|
||||
Uri uri, Map<String, IContentProvider> providerMap) {
|
||||
return Pair.create(RuntimeEnvironment.application.getPackageName(), R.drawable.ic_settings_accent);
|
||||
return Pair.create(RuntimeEnvironment.application.getPackageName(),
|
||||
R.drawable.ic_settings_accent);
|
||||
}
|
||||
|
||||
@Implementation
|
||||
|
Reference in New Issue
Block a user