diff --git a/res/color/dream_card_color_state_list.xml b/res/color/dream_card_color_state_list.xml index b0c86bba121..0799dc6fab0 100644 --- a/res/color/dream_card_color_state_list.xml +++ b/res/color/dream_card_color_state_list.xml @@ -17,6 +17,8 @@ + \ No newline at end of file diff --git a/res/color/dream_card_text_color_state_list.xml b/res/color/dream_card_text_color_state_list.xml index 438855fd233..2ca7a0fc915 100644 --- a/res/color/dream_card_text_color_state_list.xml +++ b/res/color/dream_card_text_color_state_list.xml @@ -17,6 +17,8 @@ + \ No newline at end of file diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 5ebaeed81c9..469f20198e6 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1220,14 +1220,12 @@ @string/screensaver_settings_summary_sleep @string/screensaver_settings_summary_dock @string/screensaver_settings_summary_either_long - @string/screensaver_settings_summary_never while_charging_only while_docked_only either_charging_or_docked - never diff --git a/res/values/strings.xml b/res/values/strings.xml index 5e654f96ef2..cd22b700cb3 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3168,6 +3168,8 @@ Try different styles, wallpapers, and more Screen saver + + Use screen saver While charging or docked diff --git a/res/xml/dream_fragment_overview.xml b/res/xml/dream_fragment_overview.xml index 105cec47cc7..0ca4869865b 100644 --- a/res/xml/dream_fragment_overview.xml +++ b/res/xml/dream_fragment_overview.xml @@ -19,26 +19,35 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/screensaver_settings_title"> - - - + - - + android:key="dream_main_category"> + + + + + + + + diff --git a/src/com/android/settings/dream/DreamAdapter.java b/src/com/android/settings/dream/DreamAdapter.java index f196500af5c..27c32dfbe79 100644 --- a/src/com/android/settings/dream/DreamAdapter.java +++ b/src/com/android/settings/dream/DreamAdapter.java @@ -32,6 +32,7 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.android.settings.R; +import com.android.settingslib.utils.ColorUtil; import java.util.List; @@ -43,17 +44,20 @@ public class DreamAdapter extends RecyclerView.Adapter @LayoutRes private final int mLayoutRes; private int mLastSelectedPos = -1; + private boolean mEnabled = true; /** * View holder for each {@link IDreamItem}. */ private class DreamViewHolder extends RecyclerView.ViewHolder { + private static final int VALUE_ENABLED_ALPHA = 255; private final TextView mTitleView; private final TextView mSummaryView; private final ImageView mPreviewView; private final ImageView mPreviewPlaceholderView; private final Button mCustomizeButton; private final Context mContext; + private final int mDisabledAlphaValue; DreamViewHolder(View view, Context context) { super(view); @@ -63,6 +67,7 @@ public class DreamAdapter extends RecyclerView.Adapter mTitleView = view.findViewById(R.id.title_text); mSummaryView = view.findViewById(R.id.summary_text); mCustomizeButton = view.findViewById(R.id.customize_button); + mDisabledAlphaValue = (int) (ColorUtil.getDisabledAlpha(context) * VALUE_ENABLED_ALPHA); } /** @@ -88,10 +93,11 @@ public class DreamAdapter extends RecyclerView.Adapter mPreviewView.setImageDrawable(null); mPreviewPlaceholderView.setVisibility(View.VISIBLE); } + mPreviewView.setImageAlpha(getAlpha()); final Drawable icon = item.isActive() ? mContext.getDrawable(R.drawable.ic_dream_check_circle) - : item.getIcon(); + : item.getIcon().mutate(); if (icon instanceof VectorDrawable) { icon.setTintList( mContext.getColorStateList(R.color.dream_card_icon_color_state_list)); @@ -99,6 +105,7 @@ public class DreamAdapter extends RecyclerView.Adapter final int iconSize = mContext.getResources().getDimensionPixelSize( R.dimen.dream_item_icon_size); icon.setBounds(0, 0, iconSize, iconSize); + icon.setAlpha(getAlpha()); mTitleView.setCompoundDrawablesRelative(icon, null, null, null); if (item.isActive()) { @@ -109,7 +116,8 @@ public class DreamAdapter extends RecyclerView.Adapter } mCustomizeButton.setOnClickListener(v -> item.onCustomizeClicked()); - mCustomizeButton.setVisibility(item.allowCustomization() ? View.VISIBLE : View.GONE); + mCustomizeButton.setVisibility( + item.allowCustomization() && mEnabled ? View.VISIBLE : View.GONE); itemView.setOnClickListener(v -> { item.onItemClicked(); @@ -118,6 +126,26 @@ public class DreamAdapter extends RecyclerView.Adapter } notifyItemChanged(position); }); + + setEnabledStateOnViews(itemView, mEnabled); + } + + private int getAlpha() { + return mEnabled ? VALUE_ENABLED_ALPHA : mDisabledAlphaValue; + } + + /** + * Makes sure the view (and any children) get the enabled state changed. + */ + private void setEnabledStateOnViews(@NonNull View v, boolean enabled) { + v.setEnabled(enabled); + + if (v instanceof ViewGroup) { + final ViewGroup vg = (ViewGroup) v; + for (int i = vg.getChildCount() - 1; i >= 0; i--) { + setEnabledStateOnViews(vg.getChildAt(i), enabled); + } + } } } @@ -143,4 +171,14 @@ public class DreamAdapter extends RecyclerView.Adapter public int getItemCount() { return mItemList.size(); } + + /** + * Sets the enabled state of all items. + */ + public void setEnabled(boolean enabled) { + if (mEnabled != enabled) { + mEnabled = enabled; + notifyDataSetChanged(); + } + } } diff --git a/src/com/android/settings/dream/DreamMainSwitchPreferenceController.java b/src/com/android/settings/dream/DreamMainSwitchPreferenceController.java new file mode 100644 index 00000000000..1425d3a7676 --- /dev/null +++ b/src/com/android/settings/dream/DreamMainSwitchPreferenceController.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2022 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.dream; + +import static androidx.lifecycle.Lifecycle.Event.ON_START; +import static androidx.lifecycle.Lifecycle.Event.ON_STOP; + +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; + +import androidx.lifecycle.OnLifecycleEvent; + +import com.android.settings.widget.SettingsMainSwitchPreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.dream.DreamBackend; + +/** + * Preference controller for switching dreams on/off. + */ +public class DreamMainSwitchPreferenceController extends + SettingsMainSwitchPreferenceController implements LifecycleObserver { + static final String MAIN_SWITCH_PREF_KEY = "dream_main_settings_switch"; + private final DreamBackend mBackend; + + private final ContentObserver mObserver = new ContentObserver( + new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + updateState(mSwitchPreference); + } + }; + + public DreamMainSwitchPreferenceController(Context context, String key) { + super(context, key); + mBackend = DreamBackend.getInstance(context); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean isChecked() { + return mBackend.isEnabled(); + } + + @Override + public boolean setChecked(boolean isChecked) { + mBackend.setEnabled(isChecked); + return true; + } + + @Override + public boolean isSliceable() { + return false; + } + + @Override + public int getSliceHighlightMenuRes() { + // not needed since it's not sliceable + return NO_RES; + } + + @OnLifecycleEvent(ON_START) + void onStart() { + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.SCREENSAVER_ENABLED), + /* notifyForDescendants= */ false, mObserver); + } + + @OnLifecycleEvent(ON_STOP) + void onStop() { + mContext.getContentResolver().unregisterContentObserver(mObserver); + } +} diff --git a/src/com/android/settings/dream/DreamPickerController.java b/src/com/android/settings/dream/DreamPickerController.java index 201d6de7f00..5afc4923567 100644 --- a/src/com/android/settings/dream/DreamPickerController.java +++ b/src/com/android/settings/dream/DreamPickerController.java @@ -16,11 +16,15 @@ package com.android.settings.dream; +import static com.android.settings.dream.DreamMainSwitchPreferenceController.MAIN_SWITCH_PREF_KEY; + import android.app.settings.SettingsEnums; import android.content.Context; import android.graphics.drawable.Drawable; +import android.widget.Switch; import androidx.annotation.Nullable; +import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import androidx.recyclerview.widget.RecyclerView; @@ -31,6 +35,8 @@ import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.dream.DreamBackend; import com.android.settingslib.dream.DreamBackend.DreamInfo; import com.android.settingslib.widget.LayoutPreference; +import com.android.settingslib.widget.MainSwitchPreference; +import com.android.settingslib.widget.OnMainSwitchChangeListener; import java.util.List; import java.util.stream.Collectors; @@ -38,8 +44,8 @@ import java.util.stream.Collectors; /** * Controller for the dream picker where the user can select a screensaver. */ -public class DreamPickerController extends BasePreferenceController { - private static final String KEY = "dream_picker"; +public class DreamPickerController extends BasePreferenceController implements + OnMainSwitchChangeListener { private final DreamBackend mBackend; private final MetricsFeatureProvider mMetricsFeatureProvider; @@ -48,23 +54,18 @@ public class DreamPickerController extends BasePreferenceController { private DreamInfo mActiveDream; private DreamAdapter mAdapter; - public DreamPickerController(Context context) { - this(context, DreamBackend.getInstance(context)); + public DreamPickerController(Context context, String key) { + this(context, key, DreamBackend.getInstance(context)); } - public DreamPickerController(Context context, DreamBackend backend) { - super(context, KEY); + public DreamPickerController(Context context, String key, DreamBackend backend) { + super(context, key); mBackend = backend; mDreamInfos = mBackend.getDreamInfos(); mActiveDream = getActiveDreamInfo(mDreamInfos); mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); } - @Override - public String getPreferenceKey() { - return KEY; - } - @Override public int getAvailabilityStatus() { return mDreamInfos.size() > 0 ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; @@ -79,6 +80,8 @@ public class DreamPickerController extends BasePreferenceController { .map(DreamItem::new) .collect(Collectors.toList())); + mAdapter.setEnabled(mBackend.isEnabled()); + final LayoutPreference pref = screen.findPreference(getPreferenceKey()); if (pref == null) { return; @@ -89,6 +92,11 @@ public class DreamPickerController extends BasePreferenceController { new GridSpacingItemDecoration(mContext, R.dimen.dream_preference_card_padding)); recyclerView.setHasFixedSize(true); recyclerView.setAdapter(mAdapter); + + final Preference mainSwitchPref = screen.findPreference(MAIN_SWITCH_PREF_KEY); + if (mainSwitchPref instanceof MainSwitchPreference) { + ((MainSwitchPreference) mainSwitchPref).addOnSwitchChangeListener(this); + } } @Nullable @@ -100,6 +108,13 @@ public class DreamPickerController extends BasePreferenceController { .orElse(null); } + @Override + public void onSwitchChanged(Switch switchView, boolean isChecked) { + if (mAdapter != null) { + mAdapter.setEnabled(isChecked); + } + } + private class DreamItem implements IDreamItem { DreamInfo mDreamInfo; diff --git a/src/com/android/settings/dream/DreamSettings.java b/src/com/android/settings/dream/DreamSettings.java index bfa19a9e91f..d30f50ff91b 100644 --- a/src/com/android/settings/dream/DreamSettings.java +++ b/src/com/android/settings/dream/DreamSettings.java @@ -16,6 +16,7 @@ package com.android.settings.dream; +import static com.android.settings.dream.DreamMainSwitchPreferenceController.MAIN_SWITCH_PREF_KEY; import static com.android.settingslib.dream.DreamBackend.EITHER; import static com.android.settingslib.dream.DreamBackend.NEVER; import static com.android.settingslib.dream.DreamBackend.WHILE_CHARGING; @@ -25,10 +26,13 @@ import android.app.settings.SettingsEnums; import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.Switch; import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceCategory; import androidx.recyclerview.widget.RecyclerView; import com.android.settings.R; @@ -38,12 +42,14 @@ import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.dream.DreamBackend; import com.android.settingslib.dream.DreamBackend.WhenToDream; import com.android.settingslib.search.SearchIndexable; +import com.android.settingslib.widget.MainSwitchPreference; +import com.android.settingslib.widget.OnMainSwitchChangeListener; import java.util.ArrayList; import java.util.List; @SearchIndexable -public class DreamSettings extends DashboardFragment { +public class DreamSettings extends DashboardFragment implements OnMainSwitchChangeListener { private static final String TAG = "DreamSettings"; static final String WHILE_CHARGING_ONLY = "while_charging_only"; @@ -51,6 +57,13 @@ public class DreamSettings extends DashboardFragment { static final String EITHER_CHARGING_OR_DOCKED = "either_charging_or_docked"; static final String NEVER_DREAM = "never"; + private static final String MAIN_PREF_CATEGORY = "dream_main_category"; + + private MainSwitchPreference mMainSwitchPreference; + private PreferenceCategory mMainPrefCategory; + private Button mPreviewButton; + private RecyclerView mRecyclerView; + @WhenToDream static int getSettingFromPrefKey(String key) { switch (key) { @@ -135,37 +148,58 @@ public class DreamSettings extends DashboardFragment { private static List buildPreferenceControllers(Context context) { final List controllers = new ArrayList<>(); - controllers.add(new DreamPickerController(context)); controllers.add(new WhenToDreamPreferenceController(context)); return controllers; } @Override - public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, - Bundle bundle) { - - final ViewGroup root = getActivity().findViewById(android.R.id.content); - final Button previewButton = (Button) getActivity().getLayoutInflater().inflate( - R.layout.dream_preview_button, root, false); - root.addView(previewButton); + public void onCreate(Bundle icicle) { + super.onCreate(icicle); final DreamBackend dreamBackend = DreamBackend.getInstance(getContext()); - previewButton.setOnClickListener(v -> dreamBackend.preview(dreamBackend.getActiveDream())); - final RecyclerView recyclerView = super.onCreateRecyclerView(inflater, parent, bundle); - previewButton.post(() -> { - recyclerView.setPadding(0, 0, 0, previewButton.getMeasuredHeight()); - }); - return recyclerView; + mMainSwitchPreference = findPreference(MAIN_SWITCH_PREF_KEY); + if (mMainSwitchPreference != null) { + mMainSwitchPreference.addOnSwitchChangeListener(this); + } + + mMainPrefCategory = findPreference(MAIN_PREF_CATEGORY); + if (mMainPrefCategory != null) { + mMainPrefCategory.setEnabled(dreamBackend.isEnabled()); + } } - public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER - = new BaseSearchIndexProvider(R.xml.dream_fragment_overview) { + @Override + public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, + Bundle bundle) { + final DreamBackend dreamBackend = DreamBackend.getInstance(getContext()); - @Override - public List createPreferenceControllers(Context context) { - return buildPreferenceControllers(context); - } - }; + final ViewGroup root = getActivity().findViewById(android.R.id.content); + mPreviewButton = (Button) getActivity().getLayoutInflater().inflate( + R.layout.dream_preview_button, root, false); + mPreviewButton.setVisibility(dreamBackend.isEnabled() ? View.VISIBLE : View.GONE); + root.addView(mPreviewButton); + mPreviewButton.setOnClickListener(v -> dreamBackend.preview(dreamBackend.getActiveDream())); + + mRecyclerView = super.onCreateRecyclerView(inflater, parent, bundle); + updatePaddingForPreviewButton(); + return mRecyclerView; + } + + private void updatePaddingForPreviewButton() { + mPreviewButton.post(() -> { + mRecyclerView.setPadding(0, 0, 0, mPreviewButton.getMeasuredHeight()); + }); + } + + @Override + public void onSwitchChanged(Switch switchView, boolean isChecked) { + mMainPrefCategory.setEnabled(isChecked); + mPreviewButton.setVisibility(isChecked ? View.VISIBLE : View.GONE); + updatePaddingForPreviewButton(); + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.dream_fragment_overview); } diff --git a/tests/robotests/src/com/android/settings/dream/DreamMainSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/dream/DreamMainSwitchPreferenceControllerTest.java new file mode 100644 index 00000000000..fb55164b073 --- /dev/null +++ b/tests/robotests/src/com/android/settings/dream/DreamMainSwitchPreferenceControllerTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2020 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.dream; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; + +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settingslib.dream.DreamBackend; +import com.android.settingslib.widget.MainSwitchPreference; +import com.android.settingslib.widget.OnMainSwitchChangeListener; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowContentResolver; +import org.robolectric.shadows.ShadowSettings; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowSettings.ShadowSecure.class}) +public class DreamMainSwitchPreferenceControllerTest { + + private Context mContext; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + private DreamMainSwitchPreferenceController mController; + private MainSwitchPreference mPreference; + private DreamBackend mBackend; + @Mock + private OnMainSwitchChangeListener mChangeListener; + private ShadowContentResolver mShadowContentResolver; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + mShadowContentResolver = Shadow.extract(mContext.getContentResolver()); + mBackend = DreamBackend.getInstance(mContext); + mController = new DreamMainSwitchPreferenceController(mContext, "key"); + mPreference = new MainSwitchPreference(mContext); + mPreference.setKey(mController.getPreferenceKey()); + when(mScreen.findPreference(mPreference.getKey())).thenReturn(mPreference); + mController.displayPreference(mScreen); + } + + @After + public void tearDown() { + ShadowSettings.ShadowSecure.reset(); + mController.onStop(); + } + + @Test + public void testIsChecked_returnsFalse() { + mBackend.setEnabled(false); + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void testIsChecked_returnsTrue() { + mBackend.setEnabled(true); + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void testSetChecked_setFalse_disablesSetting() { + mBackend.setEnabled(true); + mController.setChecked(false); + assertThat(mBackend.isEnabled()).isFalse(); + } + + @Test + public void testSetChecked_setTrue_enablesSetting() { + mBackend.setEnabled(false); + mController.setChecked(true); + assertThat(mBackend.isEnabled()).isTrue(); + } + + @Test + public void testIsSliceable_returnsFalse() { + assertThat(mController.isSliceable()).isFalse(); + } + + @Test + public void testRegisterAndUnregister() { + mController.onStart(); + assertThat(mShadowContentResolver.getContentObservers( + Settings.Secure.getUriFor(Settings.Secure.SCREENSAVER_ENABLED))).hasSize(1); + + mController.onStop(); + assertThat(mShadowContentResolver.getContentObservers( + Settings.Secure.getUriFor(Settings.Secure.SCREENSAVER_ENABLED))).isEmpty(); + } + + @Test + public void testUpdateState() { + mController.onStart(); + + mBackend.setEnabled(true); + triggerOnChangeListener(); + assertThat(mPreference.isChecked()).isTrue(); + + mBackend.setEnabled(false); + triggerOnChangeListener(); + assertThat(mPreference.isChecked()).isFalse(); + + mBackend.setEnabled(true); + triggerOnChangeListener(); + assertThat(mPreference.isChecked()).isTrue(); + } + + private void triggerOnChangeListener() { + mShadowContentResolver.getContentObservers( + Settings.Secure.getUriFor(Settings.Secure.SCREENSAVER_ENABLED)) + .forEach(contentObserver -> contentObserver.onChange(false)); + } +} diff --git a/tests/robotests/src/com/android/settings/dream/DreamPickerControllerTest.java b/tests/robotests/src/com/android/settings/dream/DreamPickerControllerTest.java index 401ffe0700f..7a5299f35e3 100644 --- a/tests/robotests/src/com/android/settings/dream/DreamPickerControllerTest.java +++ b/tests/robotests/src/com/android/settings/dream/DreamPickerControllerTest.java @@ -62,7 +62,8 @@ public class DreamPickerControllerTest { } private DreamPickerController buildController() { - final DreamPickerController controller = new DreamPickerController(mContext, mBackend); + final DreamPickerController controller = new DreamPickerController(mContext, "key", + mBackend); controller.displayPreference(mScreen); return controller; }