Merge "Reduce flickers of Injection" into tm-dev

This commit is contained in:
Jason Chiu
2022-05-09 11:04:20 +00:00
committed by Android (Google) Code Review
7 changed files with 103 additions and 27 deletions

View File

@@ -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();
}

View File

@@ -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);
});

View File

@@ -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
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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