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

Commit f51360e0 authored by Will Leshner's avatar Will Leshner
Browse files

Update dream settings when packages are added or removed.

This fixes a bug with the dreams grid not refreshing correctly
when a package containing a dream is added or removed under certain
conditions.

NO_IFTTT=no changes necessary

Fixes: 415125775
Test: atest DreamSettingsTest, DreamPickerControllerTest
Flag: EXEMPT bugfix

Change-Id: Ib62c996f2b00d31c00038c0fb6a9565f86216f0d
parent ce16e6a9
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -208,6 +208,11 @@ public class DreamAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
        mLayouts.append(DreamItemViewTypes.DREAM_ITEM, layoutRes);
    }

    void setItemList(List<IDreamItem> itemList) {
        mItemList.clear();
        mItemList.addAll(itemList);
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
+14 −3
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import com.android.settingslib.dream.DreamBackend;
import com.android.settingslib.dream.DreamBackend.DreamInfo;
import com.android.settingslib.widget.LayoutPreference;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
@@ -45,7 +46,7 @@ public class DreamPickerController extends BasePreferenceController {

    private final DreamBackend mBackend;
    private final MetricsFeatureProvider mMetricsFeatureProvider;
    private final List<DreamInfo> mDreamInfos;
    private final List<DreamInfo> mDreamInfos = new ArrayList<>();
    @Nullable
    private DreamInfo mActiveDream;
    private DreamAdapter mAdapter;
@@ -59,14 +60,14 @@ public class DreamPickerController extends BasePreferenceController {
    public DreamPickerController(Context context, DreamBackend backend) {
        super(context, PREF_KEY);
        mBackend = backend;
        mDreamInfos = mBackend.getDreamInfos();
        mDreamInfos.addAll(mBackend.getDreamInfos());
        mActiveDream = getActiveDreamInfo(mDreamInfos);
        mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
    }

    @Override
    public int getAvailabilityStatus() {
        return mDreamInfos.size() > 0 ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
        return !mDreamInfos.isEmpty() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
    }

    @Override
@@ -105,6 +106,16 @@ public class DreamPickerController extends BasePreferenceController {
        return mActiveDream;
    }

    void refreshDreamsList() {
        mDreamInfos.clear();
        mDreamInfos.addAll(mBackend.getDreamInfos());
        mAdapter.setItemList(mDreamInfos
                .stream()
                .map(DreamItem::new)
                .collect(Collectors.toList()));
        mAdapter.notifyDataSetChanged();
    }

    @Nullable
    private static DreamInfo getActiveDreamInfo(List<DreamInfo> dreamInfos) {
        return dreamInfos
+67 −0
Original line number Diff line number Diff line
@@ -21,8 +21,13 @@ import static android.service.dreams.Flags.dreamsV2;
import static com.android.settings.dream.DreamMainSwitchPreferenceController.MAIN_SWITCH_PREF_KEY;

import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.service.dreams.DreamService;
import android.view.LayoutInflater;
import android.view.View;
@@ -62,12 +67,14 @@ public class DreamSettings extends DashboardFragment implements OnCheckedChangeL
    static final String WHILE_POSTURED_ONLY = "while_postured_only";
    static final String NEVER_DREAM = "never";
    private static final String SPACE_PREF_KEY = "dream_space_preference";
    private static final long DREAMS_LIST_REFRESH_DELAY_MS = 1000;

    private MainSwitchPreference mMainSwitchPreference;
    private Button mPreviewButton;
    private Preference mComplicationsTogglePreference;
    private Preference mHomeControllerTogglePreference;
    private RecyclerView mRecyclerView;
    private final Handler mHandler = new Handler(Looper.getMainLooper());

    private DreamPickerController mDreamPickerController;
    private DreamHomeControlsPreferenceController mDreamHomeControlsPreferenceController;
@@ -78,6 +85,9 @@ public class DreamSettings extends DashboardFragment implements OnCheckedChangeL
            updateSelectedDreamSettingsState(
                    mMainSwitchPreference != null ? mMainSwitchPreference.isChecked() : false);

    private final Runnable mRefreshDreamsListRunnable = this::refreshDreamsList;
    private BroadcastReceiver mPackageObserver;

    @WhenToDream
    static int getSettingFromPrefKey(String key) {
        switch (key) {
@@ -210,6 +220,17 @@ public class DreamSettings extends DashboardFragment implements OnCheckedChangeL
        mLowLightModePreferenceController = controller;
    }

    @VisibleForTesting
    Handler getHandler() {
        return mHandler;
    }

    private void refreshDreamsList() {
        if (mDreamPickerController != null) {
            mDreamPickerController.refreshDreamsList();
        }
    }

    private void setAllPreferencesEnabled(boolean isEnabled) {
        getPreferenceControllers().forEach(controllers -> {
            controllers.forEach(controller -> {
@@ -255,6 +276,8 @@ public class DreamSettings extends DashboardFragment implements OnCheckedChangeL
            mDreamPickerController.addCallback(mCallback);
        }

        mPackageObserver = new PackageObserver(this::onPackagesChanged);

        // Remove the space preference manually only if the flag is enabled, which removes the
        // floating preview button, making the extra space unnecessary.
        if (dreamsV2()) {
@@ -271,6 +294,29 @@ public class DreamSettings extends DashboardFragment implements OnCheckedChangeL
        super.onDestroy();
    }

    @Override
    public void onResume() {
        super.onResume();

        // Register to receive broadcasts for package changes so that the dreams list can be
        // refreshed when packages are installed, uninstalled, or updated.
        final IntentFilter packageFilter = new IntentFilter();
        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
        packageFilter.addDataScheme("package");
        getActivity().registerReceiver(mPackageObserver, packageFilter);

        refreshDreamsList();
    }

    @Override
    public void onPause() {
        super.onPause();
        getHandler().removeCallbacks(mRefreshDreamsListRunnable);
        getActivity().unregisterReceiver(mPackageObserver);
    }

    @Override
    public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
            Bundle bundle) {
@@ -293,6 +339,14 @@ public class DreamSettings extends DashboardFragment implements OnCheckedChangeL
        return mRecyclerView;
    }

    /** Refresh the dreams list (in case a dream has just been installed or removed). */
    private void onPackagesChanged() {
        // Delay before refreshing the dreams list to account for dreams that are hosted in a
        // different package than the one that is being changed (for example, the home controls
        // dream depends on the presence of the Home app, but is actually hosted in sysui).
        getHandler().postDelayed(mRefreshDreamsListRunnable, DREAMS_LIST_REFRESH_DELAY_MS);
    }

    /**
     * Updates the visibility and enabled state of preferences that depend on the currently selected
     * dream.
@@ -348,5 +402,18 @@ public class DreamSettings extends DashboardFragment implements OnCheckedChangeL
            return Utils.areDreamsAvailableToCurrentUser(context);
        }
    }

    private static class PackageObserver extends BroadcastReceiver {
        private final Runnable mCallback;

        PackageObserver(Runnable callback) {
            mCallback = callback;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            mCallback.run();
        }
    }
}
// LINT.ThenChange(ScreensaverScreen.kt)
+15 −0
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

@RunWith(RobolectricTestRunner.class)
@@ -90,4 +91,18 @@ public class DreamPickerControllerTest {
        RecyclerView view = mPreference.findViewById(R.id.dream_list);
        assertThat(view.getAdapter().getItemCount()).isEqualTo(1);
    }

    @Test
    public void refreshDreamsListUpdatesAdapter() {
        when(mBackend.getDreamInfos()).thenReturn(Collections.singletonList(new DreamInfo()));
        final DreamPickerController controller = buildController();
        controller.updateState(mPreference);

        RecyclerView view = mPreference.findViewById(R.id.dream_list);
        assertThat(view.getAdapter().getItemCount()).isEqualTo(1);

        when(mBackend.getDreamInfos()).thenReturn(Arrays.asList(new DreamInfo(), new DreamInfo()));
        controller.refreshDreamsList();
        assertThat(view.getAdapter().getItemCount()).isEqualTo(2);
    }
}
+94 −0
Original line number Diff line number Diff line
@@ -18,18 +18,27 @@ package com.android.settings.dream;

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

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.service.dreams.DreamService;
import android.widget.CompoundButton;

import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
@@ -48,6 +57,7 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;

import java.util.Arrays;
@@ -383,6 +393,90 @@ public class DreamSettingsTest {
        verify(mLowLightModeTogglePref).setEnabled(true);
    }

    @Test
    public void onResume_dreamsAreRefreshed() {
        MockitoAnnotations.initMocks(this);

        final Context context = ApplicationProvider.getApplicationContext();
        final DreamSettings dreamSettings = spy(prepareDreamSettings(context));

        final FragmentActivity activity = spy(ActivityController.of(new FragmentActivity()).get());
        doReturn(activity).when(dreamSettings).getActivity();

        final DreamBackend.DreamInfo activeDream = new DreamBackend.DreamInfo();
        activeDream.dreamCategory = DreamService.DREAM_CATEGORY_DEFAULT;
        when(mDreamPickerController.getActiveDreamInfo()).thenReturn(activeDream);

        dreamSettings.onAttach(context);
        dreamSettings.onCreate(Bundle.EMPTY);
        dreamSettings.onResume();

        verify(mDreamPickerController).refreshDreamsList();
    }

    @Test
    public void packageObserverRegisteredAndUnregistered() {
        MockitoAnnotations.initMocks(this);

        final Context context = ApplicationProvider.getApplicationContext();
        final DreamSettings dreamSettings = spy(prepareDreamSettings(context));

        final FragmentActivity activity = spy(ActivityController.of(new FragmentActivity()).get());
        doReturn(activity).when(dreamSettings).getActivity();

        final DreamBackend.DreamInfo activeDream = new DreamBackend.DreamInfo();
        activeDream.dreamCategory = DreamService.DREAM_CATEGORY_DEFAULT;
        when(mDreamPickerController.getActiveDreamInfo()).thenReturn(activeDream);

        dreamSettings.onAttach(context);
        dreamSettings.onCreate(Bundle.EMPTY);
        dreamSettings.onResume();

        final ArgumentCaptor<BroadcastReceiver> captor =
                ArgumentCaptor.forClass(BroadcastReceiver.class);
        verify(activity).registerReceiver(captor.capture(), any());

        dreamSettings.onPause();

        verify(activity).unregisterReceiver(captor.getValue());
    }

    @Test
    public void packageChangedBroadcastRefreshesDreamsList() {
        MockitoAnnotations.initMocks(this);

        final Context context = ApplicationProvider.getApplicationContext();
        final DreamSettings dreamSettings = spy(prepareDreamSettings(context));

        final FragmentActivity activity = spy(ActivityController.of(new FragmentActivity()).get());
        doReturn(activity).when(dreamSettings).getActivity();

        final DreamBackend.DreamInfo activeDream = new DreamBackend.DreamInfo();
        activeDream.dreamCategory = DreamService.DREAM_CATEGORY_DEFAULT;
        when(mDreamPickerController.getActiveDreamInfo()).thenReturn(activeDream);

        final Handler handler = mock(Handler.class);
        when(dreamSettings.getHandler()).thenReturn(handler);

        dreamSettings.onAttach(context);
        dreamSettings.onCreate(Bundle.EMPTY);
        dreamSettings.onResume();

        clearInvocations(mDreamPickerController);

        final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
                ArgumentCaptor.forClass(BroadcastReceiver.class);
        verify(activity).registerReceiver(receiverCaptor.capture(), any());

        receiverCaptor.getValue().onReceive(context, new Intent());

        final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
        verify(handler).postDelayed(runnableCaptor.capture(), anyLong());

        runnableCaptor.getValue().run();
        verify(mDreamPickerController).refreshDreamsList();
    }

    private DreamSettings prepareDreamSettings(Context context) {
        final TestDreamSettings dreamSettings = new TestDreamSettings(context);
        final Bundle extras = new Bundle();