Loading res/xml/bluetooth_device_details_fragment.xml +1 −1 Original line number Diff line number Diff line Loading @@ -31,7 +31,7 @@ <com.android.settings.slices.SlicePreference android:key="bt_device_slice" settings:controller="com.android.settings.slices.SlicePreferenceController" settings:controller="com.android.settings.slices.BlockingSlicePrefController" settings:allowDividerBelow="true" settings:allowDividerAbove="true"/> Loading src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +2 −2 Original line number Diff line number Diff line Loading @@ -30,7 +30,7 @@ import com.android.settings.R; import com.android.settings.core.FeatureFlags; import com.android.settings.dashboard.RestrictedDashboardFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settings.slices.SlicePreferenceController; import com.android.settings.slices.BlockingSlicePrefController; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.AbstractPreferenceController; Loading Loading @@ -106,7 +106,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment if (FeatureFlagUtils.isEnabled(context, FeatureFlags.SLICE_INJECTION)) { final BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory(context) .getBluetoothFeatureProvider(context); use(SlicePreferenceController.class).setSliceUri( use(BlockingSlicePrefController.class).setSliceUri( featureProvider.getBluetoothDeviceSettingsUri(mDeviceAddress)); } } Loading src/com/android/settings/core/BasePreferenceController.java +33 −0 Original line number Diff line number Diff line Loading @@ -106,6 +106,7 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl protected final String mPreferenceKey; protected UiBlockListener mUiBlockListener; /** * Instantiate a controller as specified controller type and user-defined key. Loading Loading @@ -289,4 +290,36 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl */ public void updateRawDataToIndex(List<SearchIndexableRaw> rawData) { } /** * Set {@link UiBlockListener} * @param uiBlockListener listener to set */ public void setUiBlockListener(UiBlockListener uiBlockListener) { mUiBlockListener = uiBlockListener; } /** * Listener to invoke when background job is finished */ public interface UiBlockListener { /** * To notify client that UI related background work is finished. * (i.e. Slice is fully loaded.) * @param controller Controller that contains background work */ void onBlockerWorkFinished(BasePreferenceController controller); } /** * Used for {@link BasePreferenceController} to decide whether it is ui blocker. * If it is, entire UI will be invisible for a certain period until controller * invokes {@link UiBlockListener} * * This won't block UI thread however has similar side effect. Please use it if you * want to avoid janky animation(i.e. new preference is added in the middle of page). * * This music be used in {@link BasePreferenceController} */ public interface UiBlocker {} } No newline at end of file src/com/android/settings/dashboard/DashboardFragment.java +48 −3 Original line number Diff line number Diff line Loading @@ -56,7 +56,8 @@ import java.util.Set; */ public abstract class DashboardFragment extends SettingsPreferenceFragment implements SettingsBaseActivity.CategoryListener, Indexable, SummaryLoader.SummaryConsumer, PreferenceGroup.OnExpandButtonClickListener { SummaryLoader.SummaryConsumer, PreferenceGroup.OnExpandButtonClickListener, BasePreferenceController.UiBlockListener { private static final String TAG = "DashboardFragment"; private final Map<Class, List<AbstractPreferenceController>> mPreferenceControllers = Loading @@ -67,6 +68,7 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment private DashboardTilePlaceholderPreferenceController mPlaceholderPreferenceController; private boolean mListeningToCategoryChange; private SummaryLoader mSummaryLoader; private UiBlockerController mBlockerController; @Override public void onAttach(Context context) { Loading Loading @@ -105,6 +107,22 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment for (AbstractPreferenceController controller : controllers) { addPreferenceController(controller); } checkUiBlocker(controllers); } private void checkUiBlocker(List<AbstractPreferenceController> controllers) { final List<String> keys = new ArrayList<>(); controllers .stream() .filter(controller -> controller instanceof BasePreferenceController.UiBlocker) .forEach(controller -> { ((BasePreferenceController) controller).setUiBlockListener(this); keys.add(controller.getPreferenceKey()); }); mBlockerController = new UiBlockerController(keys); mBlockerController.start(()->updatePreferenceVisibility()); } @Override Loading Loading @@ -319,10 +337,11 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment * DashboardCategory. */ private void refreshAllPreferences(final String TAG) { final PreferenceScreen screen = getPreferenceScreen(); // First remove old preferences. if (getPreferenceScreen() != null) { if (screen != null) { // Intentionally do not cache PreferenceScreen because it will be recreated later. getPreferenceScreen().removeAll(); screen.removeAll(); } // Add resource based tiles. Loading @@ -335,6 +354,27 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment Log.d(TAG, "All preferences added, reporting fully drawn"); activity.reportFullyDrawn(); } updatePreferenceVisibility(); } private void updatePreferenceVisibility() { final PreferenceScreen screen = getPreferenceScreen(); if (screen == null) { return; } final boolean visible = mBlockerController.isBlockerFinished(); for (List<AbstractPreferenceController> controllerList : mPreferenceControllers.values()) { for (AbstractPreferenceController controller : controllerList) { final String key = controller.getPreferenceKey(); final Preference preference = screen.findPreference(key); if (preference != null) { preference.setVisible(visible && controller.isAvailable()); } } } } /** Loading Loading @@ -413,4 +453,9 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment } mSummaryLoader.setListening(true); } @Override public void onBlockerWorkFinished(BasePreferenceController controller) { mBlockerController.countDown(controller.getPreferenceKey()); } } src/com/android/settings/dashboard/UiBlockerController.java 0 → 100644 +101 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.dashboard; import android.util.Log; import androidx.annotation.NonNull; import com.android.settings.core.BasePreferenceController; import com.android.settingslib.utils.ThreadUtils; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Control ui blocker data and check whether it is finished * * @see BasePreferenceController.UiBlocker * @see BasePreferenceController.OnUiBlockListener */ public class UiBlockerController { private static final String TAG = "UiBlockerController"; private static final int TIMEOUT_MILLIS = 500; private CountDownLatch mCountDownLatch; private boolean mBlockerFinished; private Set<String> mKeys; private long mTimeoutMillis; public UiBlockerController(@NonNull List<String> keys) { this(keys, TIMEOUT_MILLIS); } public UiBlockerController(@NonNull List<String> keys, long timeout) { mCountDownLatch = new CountDownLatch(keys.size()); mBlockerFinished = keys.isEmpty(); mKeys = new HashSet<>(keys); mTimeoutMillis = timeout; } /** * Start background thread, it will invoke {@code finishRunnable} if any condition is met * * 1. Waiting time exceeds {@link #mTimeoutMillis} * 2. All background work that associated with {@link #mCountDownLatch} is finished */ public boolean start(Runnable finishRunnable) { if (mKeys.isEmpty()) { // Don't need to run finishRunnable because it doesn't start return false; } ThreadUtils.postOnBackgroundThread(() -> { try { mCountDownLatch.await(mTimeoutMillis, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Log.w(TAG, "interrupted"); } mBlockerFinished = true; ThreadUtils.postOnMainThread(finishRunnable); }); return true; } /** * Return {@code true} if all work finished */ public boolean isBlockerFinished() { return mBlockerFinished; } /** * Count down latch by {@code key}. It only count down 1 time if same key count down multiple * times. */ public boolean countDown(String key) { if (mKeys.remove(key)) { mCountDownLatch.countDown(); return true; } return false; } } Loading
res/xml/bluetooth_device_details_fragment.xml +1 −1 Original line number Diff line number Diff line Loading @@ -31,7 +31,7 @@ <com.android.settings.slices.SlicePreference android:key="bt_device_slice" settings:controller="com.android.settings.slices.SlicePreferenceController" settings:controller="com.android.settings.slices.BlockingSlicePrefController" settings:allowDividerBelow="true" settings:allowDividerAbove="true"/> Loading
src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +2 −2 Original line number Diff line number Diff line Loading @@ -30,7 +30,7 @@ import com.android.settings.R; import com.android.settings.core.FeatureFlags; import com.android.settings.dashboard.RestrictedDashboardFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settings.slices.SlicePreferenceController; import com.android.settings.slices.BlockingSlicePrefController; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.AbstractPreferenceController; Loading Loading @@ -106,7 +106,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment if (FeatureFlagUtils.isEnabled(context, FeatureFlags.SLICE_INJECTION)) { final BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory(context) .getBluetoothFeatureProvider(context); use(SlicePreferenceController.class).setSliceUri( use(BlockingSlicePrefController.class).setSliceUri( featureProvider.getBluetoothDeviceSettingsUri(mDeviceAddress)); } } Loading
src/com/android/settings/core/BasePreferenceController.java +33 −0 Original line number Diff line number Diff line Loading @@ -106,6 +106,7 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl protected final String mPreferenceKey; protected UiBlockListener mUiBlockListener; /** * Instantiate a controller as specified controller type and user-defined key. Loading Loading @@ -289,4 +290,36 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl */ public void updateRawDataToIndex(List<SearchIndexableRaw> rawData) { } /** * Set {@link UiBlockListener} * @param uiBlockListener listener to set */ public void setUiBlockListener(UiBlockListener uiBlockListener) { mUiBlockListener = uiBlockListener; } /** * Listener to invoke when background job is finished */ public interface UiBlockListener { /** * To notify client that UI related background work is finished. * (i.e. Slice is fully loaded.) * @param controller Controller that contains background work */ void onBlockerWorkFinished(BasePreferenceController controller); } /** * Used for {@link BasePreferenceController} to decide whether it is ui blocker. * If it is, entire UI will be invisible for a certain period until controller * invokes {@link UiBlockListener} * * This won't block UI thread however has similar side effect. Please use it if you * want to avoid janky animation(i.e. new preference is added in the middle of page). * * This music be used in {@link BasePreferenceController} */ public interface UiBlocker {} } No newline at end of file
src/com/android/settings/dashboard/DashboardFragment.java +48 −3 Original line number Diff line number Diff line Loading @@ -56,7 +56,8 @@ import java.util.Set; */ public abstract class DashboardFragment extends SettingsPreferenceFragment implements SettingsBaseActivity.CategoryListener, Indexable, SummaryLoader.SummaryConsumer, PreferenceGroup.OnExpandButtonClickListener { SummaryLoader.SummaryConsumer, PreferenceGroup.OnExpandButtonClickListener, BasePreferenceController.UiBlockListener { private static final String TAG = "DashboardFragment"; private final Map<Class, List<AbstractPreferenceController>> mPreferenceControllers = Loading @@ -67,6 +68,7 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment private DashboardTilePlaceholderPreferenceController mPlaceholderPreferenceController; private boolean mListeningToCategoryChange; private SummaryLoader mSummaryLoader; private UiBlockerController mBlockerController; @Override public void onAttach(Context context) { Loading Loading @@ -105,6 +107,22 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment for (AbstractPreferenceController controller : controllers) { addPreferenceController(controller); } checkUiBlocker(controllers); } private void checkUiBlocker(List<AbstractPreferenceController> controllers) { final List<String> keys = new ArrayList<>(); controllers .stream() .filter(controller -> controller instanceof BasePreferenceController.UiBlocker) .forEach(controller -> { ((BasePreferenceController) controller).setUiBlockListener(this); keys.add(controller.getPreferenceKey()); }); mBlockerController = new UiBlockerController(keys); mBlockerController.start(()->updatePreferenceVisibility()); } @Override Loading Loading @@ -319,10 +337,11 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment * DashboardCategory. */ private void refreshAllPreferences(final String TAG) { final PreferenceScreen screen = getPreferenceScreen(); // First remove old preferences. if (getPreferenceScreen() != null) { if (screen != null) { // Intentionally do not cache PreferenceScreen because it will be recreated later. getPreferenceScreen().removeAll(); screen.removeAll(); } // Add resource based tiles. Loading @@ -335,6 +354,27 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment Log.d(TAG, "All preferences added, reporting fully drawn"); activity.reportFullyDrawn(); } updatePreferenceVisibility(); } private void updatePreferenceVisibility() { final PreferenceScreen screen = getPreferenceScreen(); if (screen == null) { return; } final boolean visible = mBlockerController.isBlockerFinished(); for (List<AbstractPreferenceController> controllerList : mPreferenceControllers.values()) { for (AbstractPreferenceController controller : controllerList) { final String key = controller.getPreferenceKey(); final Preference preference = screen.findPreference(key); if (preference != null) { preference.setVisible(visible && controller.isAvailable()); } } } } /** Loading Loading @@ -413,4 +453,9 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment } mSummaryLoader.setListening(true); } @Override public void onBlockerWorkFinished(BasePreferenceController controller) { mBlockerController.countDown(controller.getPreferenceKey()); } }
src/com/android/settings/dashboard/UiBlockerController.java 0 → 100644 +101 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.dashboard; import android.util.Log; import androidx.annotation.NonNull; import com.android.settings.core.BasePreferenceController; import com.android.settingslib.utils.ThreadUtils; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Control ui blocker data and check whether it is finished * * @see BasePreferenceController.UiBlocker * @see BasePreferenceController.OnUiBlockListener */ public class UiBlockerController { private static final String TAG = "UiBlockerController"; private static final int TIMEOUT_MILLIS = 500; private CountDownLatch mCountDownLatch; private boolean mBlockerFinished; private Set<String> mKeys; private long mTimeoutMillis; public UiBlockerController(@NonNull List<String> keys) { this(keys, TIMEOUT_MILLIS); } public UiBlockerController(@NonNull List<String> keys, long timeout) { mCountDownLatch = new CountDownLatch(keys.size()); mBlockerFinished = keys.isEmpty(); mKeys = new HashSet<>(keys); mTimeoutMillis = timeout; } /** * Start background thread, it will invoke {@code finishRunnable} if any condition is met * * 1. Waiting time exceeds {@link #mTimeoutMillis} * 2. All background work that associated with {@link #mCountDownLatch} is finished */ public boolean start(Runnable finishRunnable) { if (mKeys.isEmpty()) { // Don't need to run finishRunnable because it doesn't start return false; } ThreadUtils.postOnBackgroundThread(() -> { try { mCountDownLatch.await(mTimeoutMillis, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Log.w(TAG, "interrupted"); } mBlockerFinished = true; ThreadUtils.postOnMainThread(finishRunnable); }); return true; } /** * Return {@code true} if all work finished */ public boolean isBlockerFinished() { return mBlockerFinished; } /** * Count down latch by {@code key}. It only count down 1 time if same key count down multiple * times. */ public boolean countDown(String key) { if (mKeys.remove(key)) { mCountDownLatch.countDown(); return true; } return false; } }