Add a dark theme slice

Fixes: 142476879
Test: make RunSettingsGoogleRoboTests -j ROBOTEST_FILTER=com.android.settings.homepage.contextualcards.slices.DarkThemeSliceTest
Change-Id: I4865c3c4a807bc4ac96762a882b193a3e2595019
This commit is contained in:
Sunny Shao
2019-11-04 14:51:05 +08:00
parent 8134971f00
commit abcbc79d7d
5 changed files with 309 additions and 0 deletions

View File

@@ -10132,6 +10132,12 @@
<!-- [CHAR_LIMIT=40] Positive button text in dark theme notification -->
<string name="dark_ui_settings_dialog_acknowledge">Got it</string>
<!-- [CHAR_LIMIT=50] Title string in the dark theme slice(suggestion) -->
<string name="dark_theme_slice_title">Try Dark theme</string>
<!-- [CHAR_LIMIT=50] Subtitle string in the dark theme slice(suggestion) -->
<string name="dark_theme_slice_subtitle">Helps extend battery life</string>
<!-- [CHAR LIMIT=60] Name of dev option to enable extra quick settings tiles -->
<string name="quick_settings_developer_tiles">Quick settings developer tiles</string>

View File

@@ -78,6 +78,12 @@ public class SettingsContextualCardProvider extends ContextualCardProvider {
.setCardName(CustomSliceRegistry.FACE_ENROLL_SLICE_URI.toString())
.setCardCategory(ContextualCard.Category.DEFAULT)
.build();
final ContextualCard darkThemeCard =
ContextualCard.newBuilder()
.setSliceUri(CustomSliceRegistry.DARK_THEME_SLICE_URI.toString())
.setCardName(CustomSliceRegistry.DARK_THEME_SLICE_URI.toString())
.setCardCategory(ContextualCard.Category.IMPORTANT)
.build();
final ContextualCardList cards = ContextualCardList.newBuilder()
.addCard(wifiCard)
.addCard(connectedDeviceCard)
@@ -86,6 +92,7 @@ public class SettingsContextualCardProvider extends ContextualCardProvider {
.addCard(notificationChannelCard)
.addCard(contextualAdaptiveSleepCard)
.addCard(contextualFaceSettingsCard)
.addCard(darkThemeCard)
.build();
return cards;

View File

@@ -0,0 +1,127 @@
/*
* 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.homepage.contextualcards.slices;
import static androidx.slice.builders.ListBuilder.ICON_IMAGE;
import android.annotation.ColorInt;
import android.app.PendingIntent;
import android.app.UiModeManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice;
import androidx.slice.builders.ListBuilder;
import androidx.slice.builders.SliceAction;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.slices.CustomSliceRegistry;
import com.android.settings.slices.CustomSliceable;
public class DarkThemeSlice implements CustomSliceable {
private static final String TAG = "DarkThemeSlice";
private static final int BATTERY_LEVEL_THRESHOLD = 50;
private static final int DELAY_TIME_EXECUTING_DARK_THEME = 200;
// Keep the slice even Dark theme mode changed when it is on HomePage
@VisibleForTesting
static boolean sKeepSliceShow;
@VisibleForTesting
static long sActiveUiSession = -1000;
private final Context mContext;
private final UiModeManager mUiModeManager;
public DarkThemeSlice(Context context) {
mContext = context;
mUiModeManager = context.getSystemService(UiModeManager.class);
}
@Override
public Slice getSlice() {
final long currentUiSession = FeatureFactory.getFactory(mContext)
.getSlicesFeatureProvider().getUiSessionToken();
if (currentUiSession != sActiveUiSession) {
sActiveUiSession = currentUiSession;
sKeepSliceShow = false;
}
if (!sKeepSliceShow && !isAvailable(mContext)) {
return null;
}
sKeepSliceShow = true;
final PendingIntent toggleAction = getBroadcastIntent(mContext);
@ColorInt final int color = Utils.getColorAccentDefaultColor(mContext);
final IconCompat icon =
IconCompat.createWithResource(mContext, R.drawable.dark_theme);
final boolean isChecked = mUiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_YES;
return new ListBuilder(mContext, CustomSliceRegistry.DARK_THEME_SLICE_URI,
ListBuilder.INFINITY)
.setAccentColor(color)
.addRow(new ListBuilder.RowBuilder()
.setTitle(mContext.getText(R.string.dark_theme_slice_title))
.setTitleItem(icon, ICON_IMAGE)
.setSubtitle(mContext.getText(R.string.dark_theme_slice_subtitle))
.setPrimaryAction(
SliceAction.createToggle(toggleAction, null /* actionTitle */,
isChecked)))
.build();
}
@Override
public Uri getUri() {
return CustomSliceRegistry.DARK_THEME_SLICE_URI;
}
@Override
public void onNotifyChange(Intent intent) {
final boolean isChecked = intent.getBooleanExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE,
false);
// make toggle transition more smooth before dark theme takes effect
new Handler(Looper.getMainLooper()).postDelayed(() -> {
mUiModeManager.setNightMode(
isChecked ? UiModeManager.MODE_NIGHT_YES : UiModeManager.MODE_NIGHT_NO);
}, DELAY_TIME_EXECUTING_DARK_THEME);
}
@Override
public Intent getIntent() {
return null;
}
@VisibleForTesting
boolean isAvailable(Context context) {
// checking dark theme mode.
if (mUiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_YES) {
return false;
}
// checking the current battery level
final BatteryManager batteryManager = context.getSystemService(BatteryManager.class);
final int level = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
Log.d(TAG, "battery level=" + level);
return level <= BATTERY_LEVEL_THRESHOLD;
}
}

View File

@@ -37,6 +37,7 @@ import com.android.settings.homepage.contextualcards.slices.BatteryFixSlice;
import com.android.settings.homepage.contextualcards.slices.BluetoothDevicesSlice;
import com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice;
import com.android.settings.homepage.contextualcards.slices.ContextualNotificationChannelSlice;
import com.android.settings.homepage.contextualcards.slices.DarkThemeSlice;
import com.android.settings.homepage.contextualcards.slices.FaceSetupSlice;
import com.android.settings.homepage.contextualcards.slices.LowStorageSlice;
import com.android.settings.homepage.contextualcards.slices.NotificationChannelSlice;
@@ -342,6 +343,16 @@ public class CustomSliceRegistry {
.appendPath("media_output_indicator")
.build();
/**
* Backing Uri for the Dark theme Slice.
*/
public static final Uri DARK_THEME_SLICE_URI = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(SettingsSliceProvider.SLICE_AUTHORITY)
.appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
.appendPath("dark_theme")
.build();
@VisibleForTesting
static final Map<Uri, Class<? extends CustomSliceable>> sUriToSlice;
@@ -367,6 +378,7 @@ public class CustomSliceRegistry {
sUriToSlice.put(NOTIFICATION_CHANNEL_SLICE_URI, NotificationChannelSlice.class);
sUriToSlice.put(STORAGE_SLICE_URI, StorageSlice.class);
sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class);
sUriToSlice.put(DARK_THEME_SLICE_URI, DarkThemeSlice.class);
}
public static Class<? extends CustomSliceable> getSliceClassByUri(Uri uri) {

View File

@@ -0,0 +1,157 @@
/*
* 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.homepage.contextualcards.slices;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.app.UiModeManager;
import android.content.Context;
import android.net.Uri;
import android.os.BatteryManager;
import androidx.slice.Slice;
import androidx.slice.SliceMetadata;
import androidx.slice.SliceProvider;
import androidx.slice.widget.SliceLiveData;
import com.android.settings.R;
import com.android.settings.slices.CustomSliceRegistry;
import com.android.settings.slices.SlicesFeatureProviderImpl;
import com.android.settings.testutils.FakeFeatureFactory;
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 DarkThemeSliceTest {
@Mock
private UiModeManager mUiModeManager;
@Mock
private BatteryManager mBatteryManager;
private Context mContext;
private DarkThemeSlice mDarkThemeSlice;
private FakeFeatureFactory mFeatureFactory;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
mFeatureFactory = FakeFeatureFactory.setupForTest();
mFeatureFactory.slicesFeatureProvider = new SlicesFeatureProviderImpl();
mFeatureFactory.slicesFeatureProvider.newUiSession();
doReturn(mUiModeManager).when(mContext).getSystemService(UiModeManager.class);
// Set-up specs for SliceMetadata.
SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
mDarkThemeSlice = new DarkThemeSlice(mContext);
mDarkThemeSlice.sKeepSliceShow = false;
}
@Test
public void getUri_shouldBeDarkThemeSliceUri() {
final Uri uri = mDarkThemeSlice.getUri();
assertThat(uri).isEqualTo(CustomSliceRegistry.DARK_THEME_SLICE_URI);
}
@Test
public void isAvailable_inDarkThemeMode_returnFalse() {
when(mUiModeManager.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_YES);
assertThat(mDarkThemeSlice.isAvailable(mContext)).isFalse();
}
@Test
public void isAvailable_nonDarkThemeBatteryCapacityEq100_returnFalse() {
setBatteryCapacityLevel(100);
assertThat(mDarkThemeSlice.isAvailable(mContext)).isFalse();
}
@Test
public void isAvailable_nonDarkThemeBatteryCapacityLt50_returnTrue() {
setBatteryCapacityLevel(40);
assertThat(mDarkThemeSlice.isAvailable(mContext)).isTrue();
}
@Test
public void getSlice_notAvailable_returnNull() {
when(mUiModeManager.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_YES);
assertThat(mDarkThemeSlice.getSlice()).isNull();
}
@Test
public void getSlice_newSession_notAvailable_returnNull() {
// previous displayed: yes
mDarkThemeSlice.sKeepSliceShow = true;
// Session: use original value + 1 to become a new session
mDarkThemeSlice.sActiveUiSession =
mFeatureFactory.slicesFeatureProvider.getUiSessionToken() + 1;
when(mUiModeManager.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_YES);
assertThat(mDarkThemeSlice.getSlice()).isNull();
}
@Test
public void getSlice_previouslyDisplayed_isAvailable_returnSlice() {
mDarkThemeSlice.sActiveUiSession =
mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
mDarkThemeSlice.sKeepSliceShow = true;
setBatteryCapacityLevel(40);
assertThat(mDarkThemeSlice.getSlice()).isNotNull();
}
@Test
public void getSlice_isAvailable_returnSlice() {
setBatteryCapacityLevel(40);
assertThat(mDarkThemeSlice.getSlice()).isNotNull();
}
@Test
public void getSlice_isAvailable_showTitleSubtitle() {
setBatteryCapacityLevel(40);
final Slice slice = mDarkThemeSlice.getSlice();
final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
assertThat(metadata.getTitle()).isEqualTo(
mContext.getString(R.string.dark_theme_slice_title));
assertThat(metadata.getSubtitle()).isEqualTo(
mContext.getString(R.string.dark_theme_slice_subtitle));
}
private void setBatteryCapacityLevel(int power_level) {
when(mUiModeManager.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_NO);
doReturn(mBatteryManager).when(mContext).getSystemService(BatteryManager.class);
when(mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY))
.thenReturn(power_level);
}
}