Merge "Add a dark theme slice" into qt-qpr1-dev
am: 713143df37
Change-Id: Ie026d122cb1aedd629d2860143aace2bdd599ae9
This commit is contained in:
@@ -10137,6 +10137,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>
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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) {
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user