--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/dark_ui_mode"
+ android:key="dark_ui_mode_screen"
+ settings:staticPreferenceLocation="append"
+ settings:keywords="@string/keywords_dark_ui_mode">
+</PreferenceScreen >
android:targetClass="@string/config_wallpaper_picker_class" />
</com.android.settingslib.RestrictedPreference>
- <ListPreference
+
+ <Preference
android:key="dark_ui_mode"
+ android:fragment="com.android.settings.display.DarkUISettings"
android:title="@string/dark_ui_mode"
- android:dialogTitle="@string/dark_ui_mode_title"
- android:entries="@array/dark_ui_mode_entries"
- android:entryValues="@array/dark_ui_mode_values"
- settings:keywords="@string/keywords_dark_ui_mode"
- settings:controller="com.android.settings.display.DarkUIPreferenceController" />
+ settings:searchable="false"
+ settings:controller="com.android.settings.display.DarkUIPreferenceController"/>
<!-- Cross-listed item, if you change this, also change it in power_usage_summary.xml -->
<com.android.settings.display.TimeoutListPreference
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
-public class DarkUIPreferenceController extends BasePreferenceController
- implements Preference.OnPreferenceChangeListener {
+public class DarkUIPreferenceController extends BasePreferenceController {
private UiModeManager mUiModeManager;
}
@Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- int value = mUiModeManager.getNightMode();
- ListPreference preference = screen.findPreference(getPreferenceKey());
- preference.setValue(modeToString(value));
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- mUiModeManager.setNightMode(modeToInt((String) newValue));
- refreshSummary(preference);
- return true;
- }
-
- @Override
public CharSequence getSummary() {
- return modeToDescription(mUiModeManager.getNightMode());
- }
-
- private String modeToDescription(int mode) {
- String[] values = mContext.getResources().getStringArray(R.array.dark_ui_mode_entries);
- switch (mode) {
- case UiModeManager.MODE_NIGHT_YES:
- return values[0];
- case UiModeManager.MODE_NIGHT_NO:
- case UiModeManager.MODE_NIGHT_AUTO:
- default:
- return values[1];
-
- }
- }
-
- private String modeToString(int mode) {
- switch (mode) {
- case UiModeManager.MODE_NIGHT_YES:
- return "yes";
- case UiModeManager.MODE_NIGHT_NO:
- case UiModeManager.MODE_NIGHT_AUTO:
- default:
- return "no";
-
- }
- }
-
- private int modeToInt(String mode) {
- switch (mode) {
- case "yes":
- return UiModeManager.MODE_NIGHT_YES;
- case "no":
- case "auto":
- default:
- return UiModeManager.MODE_NIGHT_NO;
- }
+ return DarkUISettingsRadioButtonsController.modeToDescription(
+ mContext, mUiModeManager.getNightMode());
}
}
--- /dev/null
+/*
+ * 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.display;
+
+import android.app.UiModeManager;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.provider.SearchIndexableResource;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import com.android.settings.R;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.widget.RadioButtonPickerFragment;
+import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.widget.CandidateInfo;
+import com.android.settingslib.widget.FooterPreference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The screen for selecting the dark theme preference for this device. Automatically updates
+ * the associated footer view with any needed information.
+ */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class DarkUISettings extends RadioButtonPickerFragment implements Indexable {
+
+ private DarkUISettingsRadioButtonsController mController;
+ private Preference mFooter;
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.dark_ui_settings;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ // TODO(b/128686189): add illustration once it is ready
+ setIllustration(0, 0);
+ mFooter = new FooterPreference(context);
+ mFooter.setIcon(android.R.color.transparent);
+ mController = new DarkUISettingsRadioButtonsController(context, mFooter);
+ }
+
+ @Override
+ protected List<? extends CandidateInfo> getCandidates() {
+ final Context context = getContext();
+ final List<CandidateInfo> candidates = new ArrayList<>();
+ candidates.add(new DarkUISettingsCandidateInfo(
+ DarkUISettingsRadioButtonsController.modeToDescription(
+ context, UiModeManager.MODE_NIGHT_YES),
+ /* summary */ null,
+ DarkUISettingsRadioButtonsController.KEY_DARK,
+ /* enabled */ true));
+ candidates.add(new DarkUISettingsCandidateInfo(
+ DarkUISettingsRadioButtonsController.modeToDescription(
+ context, UiModeManager.MODE_NIGHT_NO),
+ /* summary */ null,
+ DarkUISettingsRadioButtonsController.KEY_LIGHT,
+ /* enabled */ true));
+ return candidates;
+ }
+
+ @Override
+ protected void addStaticPreferences(PreferenceScreen screen) {
+ screen.addPreference(mFooter);
+ }
+
+ @Override
+ protected String getDefaultKey() {
+ return mController.getDefaultKey();
+ }
+
+ @Override
+ protected boolean setDefaultKey(String key) {
+ return mController.setDefaultKey(key);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.DARK_UI_SETTINGS;
+ }
+
+ static class DarkUISettingsCandidateInfo extends CandidateInfo {
+
+ private final CharSequence mLabel;
+ private final CharSequence mSummary;
+ private final String mKey;
+
+ DarkUISettingsCandidateInfo(CharSequence label, CharSequence summary, String key,
+ boolean enabled) {
+ super(enabled);
+ mLabel = label;
+ mKey = key;
+ mSummary = summary;
+ }
+
+ @Override
+ public CharSequence loadLabel() {
+ return mLabel;
+ }
+
+ @Override
+ public Drawable loadIcon() {
+ return null;
+ }
+
+ @Override
+ public String getKey() {
+ return mKey;
+ }
+
+ public CharSequence getSummary() {
+ return mSummary;
+ }
+ }
+
+ public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(
+ Context context, boolean enabled) {
+ final SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.dark_ui_settings;
+ return Arrays.asList(sir);
+ }
+ };
+}
--- /dev/null
+/*
+ * 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.display;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import androidx.preference.Preference;
+import com.android.settings.R;
+import androidx.annotation.VisibleForTesting;
+
+public class DarkUISettingsRadioButtonsController {
+
+ public static final String KEY_DARK = "key_dark_ui_settings_dark";
+ public static final String KEY_LIGHT = "key_dark_ui_settings_light";
+
+ @VisibleForTesting
+ UiModeManager mManager;
+
+ private Preference mFooter;
+
+ public DarkUISettingsRadioButtonsController(Context context, Preference footer) {
+ mManager = context.getSystemService(UiModeManager.class);
+ mFooter = footer;
+ }
+
+ public String getDefaultKey() {
+ final int mode = mManager.getNightMode();
+ updateFooter();
+ return mode == UiModeManager.MODE_NIGHT_YES ? KEY_DARK : KEY_LIGHT;
+ }
+
+ public boolean setDefaultKey(String key) {
+ switch(key) {
+ case KEY_DARK:
+ mManager.setNightMode(UiModeManager.MODE_NIGHT_YES);
+ break;
+ case KEY_LIGHT:
+ mManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
+ break;
+ default:
+ throw new IllegalStateException(
+ "Not a valid key for " + this.getClass().getSimpleName() + ": " + key);
+ }
+ updateFooter();
+ return true;
+ }
+
+ public void updateFooter() {
+ final int mode = mManager.getNightMode();
+ switch (mode) {
+ case UiModeManager.MODE_NIGHT_YES:
+ mFooter.setSummary(R.string.dark_ui_settings_dark_summary);
+ break;
+ case UiModeManager.MODE_NIGHT_NO:
+ case UiModeManager.MODE_NIGHT_AUTO:
+ default:
+ mFooter.setSummary(R.string.dark_ui_settings_light_summary);
+ }
+ }
+
+ public static String modeToDescription(Context context, int mode) {
+ final String[] values = context.getResources().getStringArray(R.array.dark_ui_mode_entries);
+ switch (mode) {
+ case UiModeManager.MODE_NIGHT_YES:
+ return values[0];
+ case UiModeManager.MODE_NIGHT_NO:
+ case UiModeManager.MODE_NIGHT_AUTO:
+ default:
+ return values[1];
+ }
+ }
+}
protected UserManager mUserManager;
protected int mUserId;
+ private int mIllustrationId;
+ private int mIllustrationPreviewId;
+ private VideoPreference mVideoPreference;
@Override
public void onAttach(Context context) {
final String systemDefaultKey = getSystemDefaultKey();
final PreferenceScreen screen = getPreferenceScreen();
screen.removeAll();
+ if (mIllustrationId != 0) {
+ addIllustration(screen);
+ }
if (!mAppendStaticPreferences) {
addStaticPreferences(screen);
}
}
}
+ /**
+ * Allows you to set an illustration at the top of this screen. Set the illustration id to 0
+ * if you want to remove the illustration.
+ * @param illustrationId The res id for the raw of the illustration.
+ * @param previewId The res id for the drawable of the illustration
+ */
+ protected void setIllustration(int illustrationId, int previewId) {
+ mIllustrationId = illustrationId;
+ mIllustrationPreviewId = previewId;
+ }
+
+ private void addIllustration(PreferenceScreen screen) {
+ mVideoPreference = new VideoPreference(getContext());
+ mVideoPreference.setVideo(mIllustrationId, mIllustrationPreviewId);
+ screen.addPreference(mVideoPreference);
+ }
+
protected abstract List<? extends CandidateInfo> getCandidates();
protected abstract String getDefaultKey();
private int mPreviewResource;
private boolean mViewVisible;
private Surface mSurface;
+ private int mAnimationId;
+
+ public VideoPreference(Context context) {
+ super(context);
+ mContext = context;
+ initialize(context, null);
+ }
public VideoPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
+ initialize(context, attrs);
+ }
+
+ private void initialize(Context context, AttributeSet attrs) {
TypedArray attributes = context.getTheme().obtainStyledAttributes(
attrs,
- com.android.settings.R.styleable.VideoPreference,
+ R.styleable.VideoPreference,
0, 0);
try {
- int animation = attributes.getResourceId(R.styleable.VideoPreference_animation, 0);
+ // if these are already set that means they were set dynamically and don't need
+ // to be loaded from xml
+ mAnimationId = mAnimationId == 0
+ ? attributes.getResourceId(R.styleable.VideoPreference_animation, 0)
+ : mAnimationId;
mVideoPath = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(context.getPackageName())
- .appendPath(String.valueOf(animation))
+ .appendPath(String.valueOf(mAnimationId))
.build();
- mPreviewResource = attributes.getResourceId(
- R.styleable.VideoPreference_preview, 0);
+ mPreviewResource = mPreviewResource == 0
+ ? attributes.getResourceId(R.styleable.VideoPreference_preview, 0)
+ : mPreviewResource;
+ if (mPreviewResource == 0 && mAnimationId == 0) {
+ return;
+ }
initMediaPlayer();
if (mMediaPlayer != null && mMediaPlayer.getDuration() > 0) {
setVisible(true);
imageView.setImageResource(mPreviewResource);
layout.setAspectRatio(mAspectRadio);
+ updateViewStates(imageView, playButton);
- video.setOnClickListener(v -> {
- if (mMediaPlayer != null) {
- if (mMediaPlayer.isPlaying()) {
- mMediaPlayer.pause();
- playButton.setVisibility(View.VISIBLE);
- mVideoPaused = true;
- } else {
- mMediaPlayer.start();
- playButton.setVisibility(View.GONE);
- mVideoPaused = false;
- }
- }
- });
+ video.setOnClickListener(v -> updateViewStates(imageView, playButton));
video.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
});
}
+ @VisibleForTesting
+ void updateViewStates(ImageView imageView, ImageView playButton) {
+ if (mMediaPlayer != null) {
+ if (mMediaPlayer.isPlaying()) {
+ mMediaPlayer.pause();
+ playButton.setVisibility(View.VISIBLE);
+ imageView.setVisibility(View.VISIBLE);
+ mVideoPaused = true;
+ } else {
+ imageView.setVisibility(View.GONE);
+ playButton.setVisibility(View.GONE);
+ mMediaPlayer.start();
+ mVideoPaused = false;
+ }
+ }
+ }
+
@Override
public void onDetached() {
releaseMediaPlayer();
releaseMediaPlayer();
}
+ /**
+ * Sets the video for this preference. If a previous video was set this one will override it
+ * and properly release any resources and re-initialize the preference to play the new video.
+ *
+ * @param videoId The raw res id of the video
+ * @param previewId The drawable res id of the preview image to use if the video fails to load.
+ */
+ public void setVideo(int videoId, int previewId) {
+ mAnimationId = videoId;
+ mPreviewResource = previewId;
+ releaseMediaPlayer();
+ initialize(mContext, null);
+ }
+
private void initMediaPlayer() {
if (mMediaPlayer == null) {
mMediaPlayer = MediaPlayer.create(mContext, mVideoPath);
+++ /dev/null
-/*
- * Copyright (C) 2018 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.display;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.UiModeManager;
-import android.content.Context;
-
-import androidx.preference.ListPreference;
-import androidx.preference.PreferenceScreen;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-@RunWith(RobolectricTestRunner.class)
-public class DarkUIPreferenceControllerTest {
-
- private Context mContext;
- @Mock
- private ListPreference mPreference;
- @Mock
- private PreferenceScreen mPreferenceScreen;
- @Mock
- private UiModeManager mUiModeManager;
- private DarkUIPreferenceController mController;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
- mController = new DarkUIPreferenceController(mContext, "dark_ui_mode");
- mController.setUiModeManager(mUiModeManager);
- when(mPreferenceScreen.findPreference(mController.getPreferenceKey()))
- .thenReturn(mPreference);
- mController.displayPreference(mPreferenceScreen);
- }
-
- @Test
- public void onPreferenceChanged_setAuto() {
- // Auto was deprecated, it should default to NO.
- mController.onPreferenceChange(mPreference, "auto");
- verify(mUiModeManager).setNightMode(eq(UiModeManager.MODE_NIGHT_NO));
- }
-
- @Test
- public void onPreferenceChanged_setNightMode() {
- mController.onPreferenceChange(mPreference, "yes");
- verify(mUiModeManager).setNightMode(eq(UiModeManager.MODE_NIGHT_YES));
- }
-
- @Test
- public void onPreferenceChanged_setDayMode() {
- mController.onPreferenceChange(mPreference, "no");
- verify(mUiModeManager).setNightMode(eq(UiModeManager.MODE_NIGHT_NO));
- }
-
- public int getCurrentMode() {
- final UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
- return uiModeManager.getNightMode();
- }
-}
--- /dev/null
+package com.android.settings.display;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import androidx.preference.Preference;
+import com.android.settings.R;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class DarkUISettingsRadioButtonsControllerTest {
+
+ @Mock
+ private UiModeManager mUiModeManager;
+ @Mock
+ private Preference mFooter;
+ private Context mContext;
+ private DarkUISettingsRadioButtonsController mController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mController = new DarkUISettingsRadioButtonsController(mContext, mFooter);
+ mController.mManager = mUiModeManager;
+ }
+
+ @Test
+ public void footerUpdatesCorrectly() {
+ doReturn(UiModeManager.MODE_NIGHT_YES).when(mUiModeManager).getNightMode();
+ mController.updateFooter();
+ verify(mFooter).setSummary(eq(R.string.dark_ui_settings_dark_summary));
+
+ doReturn(UiModeManager.MODE_NIGHT_NO).when(mUiModeManager).getNightMode();
+ mController.updateFooter();
+ verify(mFooter).setSummary(eq(R.string.dark_ui_settings_light_summary));
+ }
+
+ public int getCurrentMode() {
+ final UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
+ return uiModeManager.getNightMode();
+ }
+}
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.view.LayoutInflater;
import android.view.TextureView;
+import android.view.View;
+import android.widget.ImageView;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
public class VideoPreferenceTest {
private static final int VIDEO_WIDTH = 100;
private static final int VIDEO_HEIGHT = 150;
+
@Mock
private MediaPlayer mMediaPlayer;
+ @Mock
+ private ImageView fakePreview;
+ @Mock
+ private ImageView fakePlayButton;
private Context mContext;
private VideoPreference mVideoPreference;
private PreferenceViewHolder mPreferenceViewHolder;
(TextureView) mPreferenceViewHolder.findViewById(R.id.video_texture_view);
mVideoPreference.mAnimationAvailable = true;
mVideoPreference.mVideoReady = true;
- mVideoPreference.onBindViewHolder(mPreferenceViewHolder);
mVideoPreference.onViewInvisible();
+ mVideoPreference.onBindViewHolder(mPreferenceViewHolder);
when(mMediaPlayer.isPlaying()).thenReturn(false);
final TextureView.SurfaceTextureListener listener = video.getSurfaceTextureListener();
verify(mMediaPlayer).release();
}
+
+ @Test
+ public void updateViewStates_paused_updatesViews() {
+ when(mMediaPlayer.isPlaying()).thenReturn(true);
+ mVideoPreference.updateViewStates(fakePreview, fakePlayButton);
+ verify(fakePlayButton).setVisibility(eq(View.VISIBLE));
+ verify(fakePreview).setVisibility(eq(View.VISIBLE));
+ verify(mMediaPlayer).pause();
+ }
+
+ @Test
+ public void updateViewStates_playing_updatesViews() {
+ when(mMediaPlayer.isPlaying()).thenReturn(false);
+ mVideoPreference.updateViewStates(fakePreview, fakePlayButton);
+ verify(fakePlayButton).setVisibility(eq(View.GONE));
+ verify(fakePreview).setVisibility(eq(View.GONE));
+ verify(mMediaPlayer).start();
+ }
+
+ @Test
+ public void updateViewStates_noMediaPlayer_skips() {
+ mVideoPreference.mMediaPlayer = null;
+ mVideoPreference.updateViewStates(fakePreview, fakePlayButton);
+ verify(fakePlayButton, never()).setVisibility(anyInt());
+ verify(fakePreview, never()).setVisibility(anyInt());
+ }
}