Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aa8de12e authored by Lei Yu's avatar Lei Yu Committed by Android (Google) Code Review
Browse files

Merge "Add UiBlocker for DashboardFragment"

parents 233f3452 22904a05
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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"/>

+2 −2
Original line number Diff line number Diff line
@@ -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;
@@ -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));
        }
    }
+33 −0
Original line number Diff line number Diff line
@@ -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.
@@ -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
+48 −3
Original line number Diff line number Diff line
@@ -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 =
@@ -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) {
@@ -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
@@ -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.
@@ -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());
                }
            }
        }
    }

    /**
@@ -413,4 +453,9 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
        }
        mSummaryLoader.setListening(true);
    }

    @Override
    public void onBlockerWorkFinished(BasePreferenceController controller) {
        mBlockerController.countDown(controller.getPreferenceKey());
    }
}
+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