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

Commit 15909104 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add FutureTask approach in DashboardFragment"

parents 1a359b5b 3351c78b
Loading
Loading
Loading
Loading
+36 −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 com.android.settingslib.core.AbstractPreferenceController;

import java.util.concurrent.FutureTask;

/**
 *  {@link FutureTask} of the Controller.
 */
public class ControllerFutureTask extends FutureTask<Void> {
    private final AbstractPreferenceController mController;

    public ControllerFutureTask(ControllerTask task, Void result) {
        super(task, result);
        mController = task.getController();
    }

    AbstractPreferenceController getController() {
        return mController;
    }
}
+90 −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.app.settings.SettingsEnums;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;

import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.utils.ThreadUtils;

/**
 * A {@link Runnable} controller task. This task handle the visibility of the controller in the
 * background. Also handle the state updating in the main thread.
 */
public class ControllerTask implements Runnable {
    private static final String TAG = "ControllerTask";
    private static final int CONTROLLER_UPDATESTATE_TIME_THRESHOLD = 50;

    private final AbstractPreferenceController mController;
    private final PreferenceScreen mScreen;
    private final int mMetricsCategory;
    private final MetricsFeatureProvider mMetricsFeature;

    public ControllerTask(AbstractPreferenceController controller, PreferenceScreen screen,
            MetricsFeatureProvider metricsFeature, int metricsCategory) {
        mController = controller;
        mScreen = screen;
        mMetricsFeature = metricsFeature;
        mMetricsCategory = metricsCategory;
    }

    @Override
    public void run() {
        if (!mController.isAvailable()) {
            return;
        }

        final String key = mController.getPreferenceKey();
        if (TextUtils.isEmpty(key)) {
            Log.d(TAG, String.format("Preference key is %s in Controller %s",
                    key, mController.getClass().getSimpleName()));
            return;
        }

        final Preference preference = mScreen.findPreference(key);
        if (preference == null) {
            Log.d(TAG, String.format("Cannot find preference with key %s in Controller %s",
                    key, mController.getClass().getSimpleName()));
            return;
        }
        ThreadUtils.postOnMainThread(() -> {
            final long t = SystemClock.elapsedRealtime();
            mController.updateState(preference);
            final int elapsedTime = (int) (SystemClock.elapsedRealtime() - t);
            if (elapsedTime > CONTROLLER_UPDATESTATE_TIME_THRESHOLD) {
                Log.w(TAG, "The updateState took " + elapsedTime + " ms in Controller "
                        + mController.getClass().getSimpleName());
                if (mMetricsFeature != null) {
                    mMetricsFeature.action(SettingsEnums.PAGE_UNKNOWN,
                            SettingsEnums.ACTION_CONTROLLER_UPDATE_STATE, mMetricsCategory,
                            mController.getClass().getSimpleName(), elapsedTime);
                }
            }
        });
    }

    AbstractPreferenceController getController() {
        return mController;
    }
}
+45 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.FeatureFlagUtils;
import android.util.Log;

import androidx.annotation.CallSuper;
@@ -35,6 +36,7 @@ import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.FeatureFlags;
import com.android.settings.core.PreferenceControllerListHelper;
import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.overlay.FeatureFactory;
@@ -46,6 +48,7 @@ import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.ProviderTile;
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.search.Indexable;
import com.android.settingslib.utils.ThreadUtils;

import java.util.ArrayList;
import java.util.Arrays;
@@ -53,6 +56,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;

/**
 * Base fragment for dashboard style UI containing a list of static and dynamic setting items.
@@ -305,10 +309,23 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
                controller -> controller.displayPreference(screen));
    }

    /**
     * @return {@code true} if the underlying controllers should be executed in parallel.
     * Override this function to enable/disable the behavior.
     */
    protected boolean isParalleledControllers() {
        return false;
    }

    /**
     * Update state of each preference managed by PreferenceController.
     */
    protected void updatePreferenceStates() {
        if (isParalleledControllers() && FeatureFlagUtils.isEnabled(getContext(),
                FeatureFlags.CONTROLLER_ENHANCEMENT)) {
            updatePreferenceStatesInParallel();
            return;
        }
        final PreferenceScreen screen = getPreferenceScreen();
        Collection<List<AbstractPreferenceController>> controllerLists =
                mPreferenceControllers.values();
@@ -336,6 +353,34 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
        }
    }

    /**
     * Use parallel method to update state of each preference managed by PreferenceController.
     */
    @VisibleForTesting
    void updatePreferenceStatesInParallel() {
        final PreferenceScreen screen = getPreferenceScreen();
        final Collection<List<AbstractPreferenceController>> controllerLists =
                mPreferenceControllers.values();
        final List<ControllerFutureTask> taskList = new ArrayList<>();
        for (List<AbstractPreferenceController> controllerList : controllerLists) {
            for (AbstractPreferenceController controller : controllerList) {
                final ControllerFutureTask task = new ControllerFutureTask(
                        new ControllerTask(controller, screen, mMetricsFeatureProvider,
                                getMetricsCategory()), null /* result */);
                taskList.add(task);
                ThreadUtils.postOnBackgroundThread(task);
            }
        }

        for (ControllerFutureTask task : taskList) {
            try {
                task.get();
            } catch (InterruptedException | ExecutionException e) {
                Log.w(TAG, task.getController().getPreferenceKey() + " " + e.getMessage());
            }
        }
    }

    /**
     * Refresh all preference items, including both static prefs from xml, and dynamic items from
     * DashboardCategory.
+71 −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 static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN;

import static com.google.common.truth.Truth.assertThat;

import android.content.Context;

import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;

import com.android.settings.core.BasePreferenceController;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;

@RunWith(RobolectricTestRunner.class)
public class ControllerFutureTaskTest {
    private static final String KEY = "my_key";

    private Context mContext;
    private TestPreferenceController mTestController;
    private PreferenceScreen mScreen;

    @Before
    public void setUp() {
        mContext = RuntimeEnvironment.application;
        mTestController = new TestPreferenceController(mContext, KEY);
        final PreferenceManager preferenceManager = new PreferenceManager(mContext);
        mScreen = preferenceManager.createPreferenceScreen(mContext);
    }

    @Test
    public void getController_addTask_checkControllerKey() {
        final ControllerFutureTask futureTask = new ControllerFutureTask(
                new ControllerTask(mTestController, mScreen, null /* metricsFeature */,
                        METRICS_CATEGORY_UNKNOWN), null /* result */);

        assertThat(futureTask.getController().getPreferenceKey()).isEqualTo(KEY);
    }


    static class TestPreferenceController extends BasePreferenceController {
        TestPreferenceController(Context context, String preferenceKey) {
            super(context, preferenceKey);
        }

        @Override
        public int getAvailabilityStatus() {
            return AVAILABLE;
        }
    }
}
+124 −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 static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

import android.content.Context;

import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;

import com.android.settingslib.core.AbstractPreferenceController;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;

@RunWith(RobolectricTestRunner.class)
public class ControllerTaskTest {
    private static final String KEY = "my_key";

    private Context mContext;
    private PreferenceScreen mScreen;
    private TestPreferenceController mTestController;
    private ControllerTask mControllerTask;

    @Before
    public void setUp() {
        mContext = RuntimeEnvironment.application;
        final PreferenceManager preferenceManager = new PreferenceManager(mContext);
        mScreen = preferenceManager.createPreferenceScreen(mContext);
        mTestController = spy(new TestPreferenceController(mContext));
        mControllerTask = new ControllerTask(mTestController, mScreen, null /* metricsFeature */,
                METRICS_CATEGORY_UNKNOWN);
    }

    @Test
    public void doRun_controlNotAvailable_noRunUpdateState() {
        mTestController.setAvailable(false);

        mControllerTask.run();

        verify(mTestController, never()).updateState(any(Preference.class));
    }

    @Test
    public void doRun_emptyKey_noRunUpdateState() {
        mTestController.setKey("");

        mControllerTask.run();

        verify(mTestController, never()).updateState(any(Preference.class));
    }

    @Test
    public void doRun_preferenceNotExist_noRunUpdateState() {
        mTestController.setKey(KEY);

        mControllerTask.run();

        verify(mTestController, never()).updateState(any(Preference.class));
    }

    @Test
    public void doRun_executeUpdateState() {
        mTestController.setKey(KEY);
        final Preference preference = new Preference(mContext);
        preference.setKey(KEY);
        mScreen.addPreference(preference);

        mControllerTask.run();

        verify(mTestController).updateState(any(Preference.class));
    }

    static class TestPreferenceController extends AbstractPreferenceController {
        private boolean mAvailable;
        private String mKey;

        TestPreferenceController(Context context) {
            super(context);
            mAvailable = true;
        }

        @Override
        public boolean isAvailable() {
            return mAvailable;
        }

        @Override
        public String getPreferenceKey() {
            return mKey;
        }

        void setAvailable(boolean available) {
            mAvailable = available;
        }

        void setKey(String key) {
            mKey = key;
        }
    }
}
Loading